From 2c7206572de6637c9a3d0c2bee73d41cd8205ec7 Mon Sep 17 00:00:00 2001 From: Ladislav Thon Date: Fri, 8 Nov 2024 16:56:49 +0100 Subject: [PATCH] introduce a second version of the programmatic API This includes the `Guard` and `TypedGuard` types, together with the `@ApplyGuard` interceptor binding annotation. These types unify synchronous and asynchronous invocations, using the new fault tolerance strategies introduced earlier. The `Guard` and `TypedGuard` interfaces, as well as their implementations, currently experience rather heavy code duplication; this can be solved in the future (probably by generating the code from a template). The first version of the programmatic API (`FaultTolerance` and `@ApplyFaultTolerance`) continues to exist, but is deprecated for removal. It will be removed in SmallRye Fault Tolerance 7.0. --- api/pom.xml | 6 + .../api/ApplyFaultTolerance.java | 47 +- .../faulttolerance/api/ApplyGuard.java | 61 + .../api/CircuitBreakerMaintenance.java | 18 +- .../faulttolerance/api/FaultTolerance.java | 940 +++------------ .../io/smallrye/faulttolerance/api/Guard.java | 946 +++++++++++++++ .../api/{FaultToleranceSpi.java => Spi.java} | 9 +- ...ToleranceSpiAccess.java => SpiAccess.java} | 16 +- .../faulttolerance/api/TypedGuard.java | 1012 +++++++++++++++++ .../api/GuardBuilderConsistencyTest.java | 59 + doc/modules/ROOT/pages/internals/logging.adoc | 2 +- .../pages/reference/programmatic-api.adoc | 361 +++--- .../ROOT/pages/reference/reusable.adoc | 50 +- .../autoconfig/FaultToleranceMethod.java | 3 + .../apiimpl/BasicMeteredOperationImpl.java | 10 +- .../core/apiimpl/FaultToleranceImpl.java | 10 +- .../core/apiimpl/GuardCommon.java | 69 ++ .../core/apiimpl/GuardImpl.java | 859 ++++++++++++++ .../core/apiimpl/LazyGuard.java | 58 + .../core/apiimpl/LazyTypedGuard.java | 46 + .../core/apiimpl/TypedGuardImpl.java | 944 +++++++++++++++ .../core/async/SyncAsyncSplit.java | 26 + .../metrics/DelegatingMeteredOperation.java | 4 +- .../core/metrics/MeteredOperation.java | 2 +- .../core/metrics/MetricsCollector.java | 8 +- .../metrics/MicroProfileMetricsRecorder.java | 2 +- .../core/metrics/MicrometerRecorder.java | 2 +- .../core/metrics/OpenTelemetryRecorder.java | 2 +- .../core/util/DirectExecutor.java | 19 - .../core/metrics/GeneralMetricsTest.java | 2 +- .../io/smallrye/faulttolerance/CdiLogger.java | 3 + ...{CdiFaultToleranceSpi.java => CdiSpi.java} | 24 +- .../FaultToleranceExtension.java | 45 +- .../FaultToleranceInterceptor.java | 161 ++- .../config/ApplyGuardConfig.java | 20 + .../config/FaultToleranceMethods.java | 3 + .../config/FaultToleranceOperation.java | 27 + .../internal/StrategyCache.java | 8 + .../metrics/CdiMeteredOperationImpl.java | 2 +- ...llrye.faulttolerance.api.FaultToleranceSpi | 1 - .../io.smallrye.faulttolerance.api.Spi | 1 + implementation/mutiny/pom.xml | 4 - .../mutiny/api/MutinyFaultTolerance.java | 47 +- .../mutiny/test/MutinyBulkheadTest.java | 5 +- .../mutiny/test/MutinyCircuitBreakerTest.java | 26 +- .../mutiny/test/MutinyFallbackTest.java | 27 +- .../mutiny/test/MutinyPassthroughTest.java | 33 +- .../mutiny/test/MutinyResubscriptionTest.java | 7 +- .../mutiny/test/MutinyRetryTest.java | 22 +- .../mutiny/test/MutinyTimeoutTest.java | 12 +- ...tTest.java => MutinyUntypedGuardTest.java} | 106 +- .../faulttolerance/mutiny/test/Types.java | 13 + implementation/standalone/pom.xml | 4 - ...ltToleranceSpi.java => StandaloneSpi.java} | 22 +- ...llrye.faulttolerance.api.FaultToleranceSpi | 1 - .../io.smallrye.faulttolerance.api.Spi | 1 + .../StandaloneBulkheadAsyncEventsTest.java | 4 +- .../test/StandaloneBulkheadAsyncTest.java | 4 +- .../test/StandaloneBulkheadEventsTest.java | 4 +- .../test/StandaloneBulkheadTest.java | 4 +- ...andaloneCircuitBreakerAsyncEventsTest.java | 10 +- .../StandaloneCircuitBreakerAsyncTest.java | 25 +- .../StandaloneCircuitBreakerEventsTest.java | 10 +- ...andaloneCircuitBreakerMaintenanceTest.java | 14 +- .../test/StandaloneCircuitBreakerTest.java | 25 +- .../test/StandaloneFallbackAsyncTest.java | 41 +- .../test/StandaloneFallbackTest.java | 27 +- .../test/StandaloneMetricsTest.java | 7 +- .../test/StandaloneMetricsTimerTest.java | 7 +- .../test/StandalonePassthroughAsyncTest.java | 53 +- .../test/StandalonePassthroughTest.java | 44 +- .../StandaloneRateLimitAsyncEventsTest.java | 4 +- .../test/StandaloneRateLimitAsyncTest.java | 4 +- .../test/StandaloneRateLimitEventsTest.java | 4 +- .../test/StandaloneRateLimitTest.java | 4 +- .../test/StandaloneRetryAsyncEventsTest.java | 7 +- .../test/StandaloneRetryAsyncTest.java | 44 +- .../test/StandaloneRetryEventsTest.java | 7 +- .../standalone/test/StandaloneRetryTest.java | 27 +- .../test/StandaloneThreadOffloadTest.java | 6 +- .../StandaloneTimeoutAsyncEventsTest.java | 7 +- .../test/StandaloneTimeoutAsyncTest.java | 7 +- .../test/StandaloneTimeoutEventsTest.java | 7 +- .../test/StandaloneTimeoutTest.java | 7 +- ...a => StandaloneUntypedAsyncGuardTest.java} | 96 +- ...t.java => StandaloneUntypedGuardTest.java} | 75 +- .../faulttolerance/standalone/test/Types.java | 13 + .../io/smallrye/faulttolerance/Types.java | 15 + ...reakerMaintenanceInteroperabilityTest.java | 5 +- .../maintenance/HelloService.java | 7 +- .../CdiFaultToleranceCopyAsyncTest.java | 8 - .../CdiFaultToleranceCopyTest.java | 8 - .../programmatic/CdiMetricsTest.java | 7 +- .../programmatic/CdiMetricsTimerTest.java | 8 +- .../CdiSkipFaultToleranceTest.java | 7 +- .../CdiUntypedAsyncGuardTest.java | 8 + .../programmatic/CdiUntypedGuardTest.java | 8 + .../completionstage/MyFaultTolerance.java | 5 +- .../async/completionstage/MyService.java | 4 +- .../metrics/MyFaultTolerance.java | 5 +- .../completionstage/metrics/MyService.java | 6 +- .../reuse/async/uni/MyFaultTolerance.java | 6 +- .../reuse/async/uni/MyService.java | 4 +- .../async/uni/metrics/MyFaultTolerance.java | 6 +- .../reuse/async/uni/metrics/MyService.java | 6 +- ...tipleGuardsWithSameIdentifierProducer.java | 19 + .../MultipleGuardsWithSameIdentifierTest.java | 16 + .../async/vs/async/MyFaultTolerance.java | 16 - .../mismatch/async/vs/async/MyService.java | 15 - .../async/ReuseMismatchAsyncVsAsyncTest.java | 23 - .../async/vs/sync/MyFaultTolerance.java | 16 - .../mismatch/async/vs/sync/MyService.java | 13 - .../vs/sync/ReuseMismatchAsyncVsSyncTest.java | 21 - .../mismatch/sync/vs/async/MyService.java | 15 - .../async/ReuseMismatchSyncVsAsyncTest.java | 21 - .../reuse/missing/MyService.java | 4 +- .../reuse/missing/ReuseMissingTest.java | 4 +- .../reuse/mixed/all/MixedReuseAllTest.java | 26 + .../async => mixed/all}/MyFaultTolerance.java | 8 +- .../reuse/mixed/all/MyService.java | 43 + .../all/metrics/MixedReuseAllMetricsTest.java | 90 ++ .../mixed/all/metrics/MyFaultTolerance.java | 16 + .../reuse/mixed/all/metrics/MyService.java | 39 + .../async/both/MixedReuseAsyncBothTest.java | 23 + .../mixed/async/both/MyFaultTolerance.java | 16 + .../reuse/mixed/async/both/MyService.java | 34 + .../completionstage/MyFaultTolerance.java | 7 +- .../async/completionstage/MyService.java | 6 +- .../mixed/async/uni/MyFaultTolerance.java | 7 +- .../reuse/mixed/async/uni/MyService.java | 6 +- .../reuse/mixed/sync/MyFaultTolerance.java | 5 +- .../reuse/mixed/sync/MyService.java | 6 +- .../reuse/sync/MyFaultTolerance.java | 4 +- .../faulttolerance/reuse/sync/MyService.java | 4 +- .../reuse/sync/metrics/MyFaultTolerance.java | 4 +- .../reuse/sync/metrics/MyService.java | 6 +- .../kotlin/reuse/MyFaultTolerance.kt | 12 +- .../faulttolerance/kotlin/reuse/MyService.kt | 7 +- .../kotlin/reuse/metrics/MyFaultTolerance.kt | 12 +- .../kotlin/reuse/metrics/MyService.kt | 8 +- 140 files changed, 5616 insertions(+), 1895 deletions(-) create mode 100644 api/src/main/java/io/smallrye/faulttolerance/api/ApplyGuard.java create mode 100644 api/src/main/java/io/smallrye/faulttolerance/api/Guard.java rename api/src/main/java/io/smallrye/faulttolerance/api/{FaultToleranceSpi.java => Spi.java} (74%) rename api/src/main/java/io/smallrye/faulttolerance/api/{FaultToleranceSpiAccess.java => SpiAccess.java} (62%) create mode 100644 api/src/main/java/io/smallrye/faulttolerance/api/TypedGuard.java create mode 100644 api/src/test/java/io/smallrye/faulttolerance/api/GuardBuilderConsistencyTest.java create mode 100644 implementation/core/src/main/java/io/smallrye/faulttolerance/core/apiimpl/GuardCommon.java create mode 100644 implementation/core/src/main/java/io/smallrye/faulttolerance/core/apiimpl/GuardImpl.java create mode 100644 implementation/core/src/main/java/io/smallrye/faulttolerance/core/apiimpl/LazyGuard.java create mode 100644 implementation/core/src/main/java/io/smallrye/faulttolerance/core/apiimpl/LazyTypedGuard.java create mode 100644 implementation/core/src/main/java/io/smallrye/faulttolerance/core/apiimpl/TypedGuardImpl.java create mode 100644 implementation/core/src/main/java/io/smallrye/faulttolerance/core/async/SyncAsyncSplit.java delete mode 100644 implementation/core/src/main/java/io/smallrye/faulttolerance/core/util/DirectExecutor.java rename implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/{CdiFaultToleranceSpi.java => CdiSpi.java} (83%) create mode 100644 implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/config/ApplyGuardConfig.java delete mode 100644 implementation/fault-tolerance/src/main/resources/META-INF/services/io.smallrye.faulttolerance.api.FaultToleranceSpi create mode 100644 implementation/fault-tolerance/src/main/resources/META-INF/services/io.smallrye.faulttolerance.api.Spi rename implementation/mutiny/src/test/java/io/smallrye/faulttolerance/mutiny/test/{MutinyFaultToleranceCastTest.java => MutinyUntypedGuardTest.java} (58%) create mode 100644 implementation/mutiny/src/test/java/io/smallrye/faulttolerance/mutiny/test/Types.java rename implementation/standalone/src/main/java/io/smallrye/faulttolerance/standalone/{StandaloneFaultToleranceSpi.java => StandaloneSpi.java} (64%) delete mode 100644 implementation/standalone/src/main/resources/META-INF/services/io.smallrye.faulttolerance.api.FaultToleranceSpi create mode 100644 implementation/standalone/src/main/resources/META-INF/services/io.smallrye.faulttolerance.api.Spi rename implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/{StandaloneFaultToleranceCastAsyncTest.java => StandaloneUntypedAsyncGuardTest.java} (62%) rename implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/{StandaloneFaultToleranceCastTest.java => StandaloneUntypedGuardTest.java} (53%) create mode 100644 implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/Types.java create mode 100644 testsuite/basic/src/test/java/io/smallrye/faulttolerance/Types.java delete mode 100644 testsuite/basic/src/test/java/io/smallrye/faulttolerance/programmatic/CdiFaultToleranceCopyAsyncTest.java delete mode 100644 testsuite/basic/src/test/java/io/smallrye/faulttolerance/programmatic/CdiFaultToleranceCopyTest.java create mode 100644 testsuite/basic/src/test/java/io/smallrye/faulttolerance/programmatic/CdiUntypedAsyncGuardTest.java create mode 100644 testsuite/basic/src/test/java/io/smallrye/faulttolerance/programmatic/CdiUntypedGuardTest.java create mode 100644 testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/errors/MultipleGuardsWithSameIdentifierProducer.java create mode 100644 testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/errors/MultipleGuardsWithSameIdentifierTest.java delete mode 100644 testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mismatch/async/vs/async/MyFaultTolerance.java delete mode 100644 testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mismatch/async/vs/async/MyService.java delete mode 100644 testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mismatch/async/vs/async/ReuseMismatchAsyncVsAsyncTest.java delete mode 100644 testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mismatch/async/vs/sync/MyFaultTolerance.java delete mode 100644 testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mismatch/async/vs/sync/MyService.java delete mode 100644 testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mismatch/async/vs/sync/ReuseMismatchAsyncVsSyncTest.java delete mode 100644 testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mismatch/sync/vs/async/MyService.java delete mode 100644 testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mismatch/sync/vs/async/ReuseMismatchSyncVsAsyncTest.java create mode 100644 testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/all/MixedReuseAllTest.java rename testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/{mismatch/sync/vs/async => mixed/all}/MyFaultTolerance.java (53%) create mode 100644 testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/all/MyService.java create mode 100644 testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/all/metrics/MixedReuseAllMetricsTest.java create mode 100644 testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/all/metrics/MyFaultTolerance.java create mode 100644 testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/all/metrics/MyService.java create mode 100644 testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/async/both/MixedReuseAsyncBothTest.java create mode 100644 testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/async/both/MyFaultTolerance.java create mode 100644 testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/async/both/MyService.java diff --git a/api/pom.xml b/api/pom.xml index 1e15b979..d07cd1b7 100644 --- a/api/pom.xml +++ b/api/pom.xml @@ -40,5 +40,11 @@ io.smallrye.common smallrye-common-annotation + + + org.junit.jupiter + junit-jupiter + test + diff --git a/api/src/main/java/io/smallrye/faulttolerance/api/ApplyFaultTolerance.java b/api/src/main/java/io/smallrye/faulttolerance/api/ApplyFaultTolerance.java index 3f13f549..4fbbd5c4 100644 --- a/api/src/main/java/io/smallrye/faulttolerance/api/ApplyFaultTolerance.java +++ b/api/src/main/java/io/smallrye/faulttolerance/api/ApplyFaultTolerance.java @@ -12,49 +12,9 @@ import io.smallrye.common.annotation.Experimental; /** - * A special interceptor binding annotation to apply preconfigured fault tolerance. - * If {@code @ApplyFaultTolerance("")} is present on a business method, - * then a bean of type {@link FaultTolerance} with qualifier - * {@link io.smallrye.common.annotation.Identifier @Identifier("<identifier>")} - * must exist. Such bean serves as a preconfigured set of fault tolerance strategies - * and is used to guard invocations of the annotated business method(s). - *

- * It is customary to create such bean by declaring a {@code static} producer field. - * That removes all scoping concerns, because only one instance ever exists. Using - * a non-static producer field or a producer method means that scoping must be carefully - * considered, especially if stateful fault tolerance strategies are configured. - *

- * The {@code @ApplyFaultTolerance} annotation may also be present on a bean class, - * in which case it applies to all business methods declared by the class. If the - * annotation is present both on the method and the class declaring the method, - * the one on the method takes precedence. - *

- * When {@code @ApplyFaultTolerance} applies to a business method, all other fault tolerance - * annotations that would otherwise also apply to that method are ignored. - *

- * A single preconfigured fault tolerance can be applied to multiple methods, as long as asynchrony - * of all those methods is the same as the asynchrony of the fault tolerance instance. For example, - * if the fault tolerance instance is created using {@code FaultTolerance.create()}, it can be - * applied to all synchronous methods, but not to any asynchronous method. If the fault tolerance - * instance is created using {@code FaultTolerance.createAsync()}, it can be applied to all - * asynchronous methods that return {@code CompletionStage}, but not to synchronous methods or - * asynchronous methods that return any other asynchronous type. - *

- * A single preconfigured fault tolerance can even be applied to multiple methods with different - * return types, as long as the constraint on method asynchrony described above is obeyed. In such - * case, it is customary to declare the fault tolerance instance as {@code FaultTolerance} - * for synchronous methods, {@code FaultTolerance>} for asynchronous - * methods that return {@code CompletionStage}, and so on. Note that this effectively precludes - * defining a useful fallback, because fallback can only be defined when the value type is known. - *

- * Note that this annotation has the same differences to the standard MicroProfile Fault Tolerance - * as {@link FaultTolerance}: - *

    - *
  • asynchronous actions of type {@link java.util.concurrent.Future} are not supported;
  • - *
  • the fallback, circuit breaker and retry strategies always inspect the cause chain of exceptions, - * following the behavior of SmallRye Fault Tolerance in the non-compatible mode.
  • - *
+ * @deprecated use {@link ApplyGuard} */ +@Deprecated(forRemoval = true) @Inherited @Documented @Retention(RetentionPolicy.RUNTIME) @@ -63,7 +23,8 @@ @Experimental("first attempt at providing reusable fault tolerance strategies") public @interface ApplyFaultTolerance { /** - * The identifier of a preconfigured fault tolerance instance. + * @deprecated use {@link ApplyGuard#value()} */ + @Deprecated(forRemoval = true) String value(); } diff --git a/api/src/main/java/io/smallrye/faulttolerance/api/ApplyGuard.java b/api/src/main/java/io/smallrye/faulttolerance/api/ApplyGuard.java new file mode 100644 index 00000000..4a75cd3f --- /dev/null +++ b/api/src/main/java/io/smallrye/faulttolerance/api/ApplyGuard.java @@ -0,0 +1,61 @@ +package io.smallrye.faulttolerance.api; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import jakarta.interceptor.InterceptorBinding; + +import io.smallrye.common.annotation.Experimental; + +/** + * A special interceptor binding annotation to apply preconfigured fault tolerance. + * If {@code @ApplyGuard("")} is present on a business method, + * then a bean of type {@link Guard} or {@link TypedGuard} with qualifier + * {@link io.smallrye.common.annotation.Identifier @Identifier("<identifier>")} + * must exist. Such bean serves as a preconfigured set of fault tolerance strategies + * and is used to guard invocations of the annotated business method(s). + *

+ * It is customary to create such bean by declaring a {@code static} producer field. + * That removes all scoping concerns, because only one instance ever exists. Using + * a non-static producer field or a producer method means that scoping must be carefully + * considered, especially if stateful fault tolerance strategies are configured. + *

+ * The {@code @ApplyGuard} annotation may also be present on a bean class, + * in which case it applies to all business methods declared by the class. If the + * annotation is present both on the method and the class declaring the method, + * the one on the method takes precedence. + *

+ * When {@code @ApplyGuard} applies to a business method, all other fault tolerance + * annotations that would otherwise also apply to that method are ignored. + *

+ * A single preconfigured fault tolerance can be applied to multiple methods. + * If the preconfigured fault tolerance is of type {@code TypedGuard}, then all methods + * must have the same return type. If the preconfigured fault tolerance is of type {@code Guard}, + * no such requirement applies; note that in this case, there is no way to define a fallback. + *

+ * Note that this annotation has the same differences to standard MicroProfile Fault Tolerance + * as {@code Guard} / {@code TypedGuard}: + *

    + *
  • asynchronous actions of type {@link java.util.concurrent.Future} are not supported;
  • + *
  • the fallback, circuit breaker and retry strategies always inspect the cause chain of exceptions, + * following the behavior of SmallRye Fault Tolerance in the non-compatible mode.
  • + *
+ * If multiple beans of type {@code Guard} or {@code TypedGuard} with the same identifier + * exist, a deployment problem occurs. + */ +@Inherited +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.METHOD, ElementType.TYPE }) +@InterceptorBinding +@Experimental("second attempt at providing reusable fault tolerance strategies") +public @interface ApplyGuard { + /** + * The identifier of a preconfigured {@link Guard} or {@link TypedGuard} instance. + */ + String value(); +} diff --git a/api/src/main/java/io/smallrye/faulttolerance/api/CircuitBreakerMaintenance.java b/api/src/main/java/io/smallrye/faulttolerance/api/CircuitBreakerMaintenance.java index 201e6298..bc9b3500 100644 --- a/api/src/main/java/io/smallrye/faulttolerance/api/CircuitBreakerMaintenance.java +++ b/api/src/main/java/io/smallrye/faulttolerance/api/CircuitBreakerMaintenance.java @@ -20,19 +20,27 @@ import io.smallrye.common.annotation.Experimental; /** - * Allows reading and observing current state of circuit breakers and reseting them to the initial (closed) state. + * Allows reading and observing current state of circuit breakers and resetting them to the initial (closed) state. * To access a specific circuit breaker, it must be given a name using {@link CircuitBreakerName @CircuitBreakerName} - * or {@link FaultTolerance.Builder.CircuitBreakerBuilder#name(String) withCircuitBreaker().name("...")}. + * or {@link Guard.Builder.CircuitBreakerBuilder#name(String) withCircuitBreaker().name("...")}. */ @Experimental("first attempt at providing maintenance access to circuit breakers") public interface CircuitBreakerMaintenance { + /** + * Returns a {@link CircuitBreakerMaintenance} instance that provides maintenance access to existing + * circuit breakers. + */ + static CircuitBreakerMaintenance get() { + return SpiAccess.get().circuitBreakerMaintenance(); + } + /** * Returns current state of the circuit breaker with given {@code name}. * Note that there's no guarantee that the circuit breaker will stay in that state for any time, * so this method is only useful for monitoring. *

* It is an error to use a {@code name} that wasn't registered using {@link CircuitBreakerName @CircuitBreakerName} - * or {@link FaultTolerance.Builder.CircuitBreakerBuilder#name(String) withCircuitBreaker().name("...")}. + * or {@link Guard.Builder.CircuitBreakerBuilder#name(String) withCircuitBreaker().name("...")}. */ CircuitBreakerState currentState(String name); @@ -41,7 +49,7 @@ public interface CircuitBreakerMaintenance { * changes state. *

* It is an error to use a {@code name} that wasn't registered using {@link CircuitBreakerName @CircuitBreakerName} - * or {@link FaultTolerance.Builder.CircuitBreakerBuilder#name(String) withCircuitBreaker().name("...")}. + * or {@link Guard.Builder.CircuitBreakerBuilder#name(String) withCircuitBreaker().name("...")}. *

* The callback must be fast and non-blocking and must not throw an exception. */ @@ -54,7 +62,7 @@ public interface CircuitBreakerMaintenance { * and perhaps emergency maintenance tasks. *

* It is an error to use a {@code name} that wasn't registered using {@link CircuitBreakerName @CircuitBreakerName} - * or {@link FaultTolerance.Builder.CircuitBreakerBuilder#name(String) withCircuitBreaker().name("...")}. + * or {@link Guard.Builder.CircuitBreakerBuilder#name(String) withCircuitBreaker().name("...")}. */ void reset(String name); diff --git a/api/src/main/java/io/smallrye/faulttolerance/api/FaultTolerance.java b/api/src/main/java/io/smallrye/faulttolerance/api/FaultTolerance.java index 597be6d7..21ffec31 100644 --- a/api/src/main/java/io/smallrye/faulttolerance/api/FaultTolerance.java +++ b/api/src/main/java/io/smallrye/faulttolerance/api/FaultTolerance.java @@ -12,153 +12,108 @@ import java.util.function.Predicate; import java.util.function.Supplier; -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 jakarta.enterprise.util.TypeLiteral; import io.smallrye.common.annotation.Experimental; /** - * Allows guarding an action with various fault tolerance strategies: bulkhead, circuit breaker, fallback, rate limit, - * retry, and timeout. Synchronous as well as asynchronous actions may be guarded, asynchronous actions may optionally - * be offloaded to another thread. The only supported type for asynchronous actions is {@link CompletionStage}. - *

- * An instance of this interface represents a configured set of fault tolerance strategies. It can be used to - * guard a {@link #call(Callable) Callable}, {@link #get(Supplier) Supplier} or {@link #run(Runnable) Runnable} - * invocation, or adapt an unguarded {@link #adaptCallable(Callable) Callable}, {@link #adaptSupplier(Supplier) Supplier} - * or {@link #adaptRunnable(Runnable) Runnable} to a guarded one. - *

- * The {@code create*} and {@code createAsync*} methods return a builder that allows configuring all fault tolerance - * strategies. Order of builder method invocations does not matter, the fault tolerance strategies are always applied - * in a predefined order: fallback > retry > circuit breaker > rate limit > timeout > bulkhead > - * thread offload > guarded action. - *

- * Two styles of usage are possible. The {@link #createCallable(Callable)} and {@link #createAsyncCallable(Callable)} - * methods return a builder that, at the end, returns a {@code Callable}. This is convenient in case you only want to - * guard a single action (which is possibly invoked multiple times). Similar methods exist for the {@code Supplier} - * and {@code Runnable} types. - *

- * The {@link #create()} and {@link #createAsync()} methods return a builder that, at the end, returns - * a {@code FaultTolerance} instance, which is useful when you need to guard multiple actions with the same set - * of fault tolerance strategies. Note that bulkheads, circuit breakers and rate limits are stateful, so there's - * a big difference between guarding multiple actions using the same {@code FaultTolerance} object and using a separate - * {@code FaultTolerance} object for each action. Using a single {@code FaultTolerance} instance to guard multiple - * actions means that a single bulkhead, circuit breaker and/or rate limit will be shared among all those actions. - *

- * This API is essentially a programmatic equivalent to the declarative, annotation-based API of MicroProfile Fault - * Tolerance and SmallRye Fault Tolerance. It shares the set of fault tolerance strategies, their invocation order - * and behavior, their configuration properties, etc. Notable differences are: - *

    - *
  • asynchronous actions of type {@link java.util.concurrent.Future} are not supported;
  • - *
  • the fallback, circuit breaker and retry strategies always inspect the cause chain of exceptions, - * following the behavior of SmallRye Fault Tolerance in the non-compatible mode.
  • - *
- * - * @param type of value of the guarded action + * @deprecated use {@link Guard} or {@link TypedGuard} */ +@Deprecated(forRemoval = true) @Experimental("first attempt at providing programmatic API") public interface FaultTolerance { /** - * Returns a {@link CircuitBreakerMaintenance} instance that provides maintenance access to existing - * circuit breakers. + * @deprecated use {@link CircuitBreakerMaintenance#get()} */ + @Deprecated(forRemoval = true) static CircuitBreakerMaintenance circuitBreakerMaintenance() { - return FaultToleranceSpiAccess.get().circuitBreakerMaintenance(); + return SpiAccess.get().circuitBreakerMaintenance(); } /** - * Returns a builder that, at the end, returns a {@link Callable} guarding the given {@code action}. - * The {@code action} is synchronous and is always executed on the original thread. + * @deprecated use {@link Guard} or {@link TypedGuard} and {@code adaptCallable} */ + @Deprecated(forRemoval = true) static Builder> createCallable(Callable action) { - return FaultToleranceSpiAccess.get().newBuilder(ft -> ft.adaptCallable(action)); + return SpiAccess.get().newBuilder(ft -> ft.adaptCallable(action)); } /** - * Returns a builder that, at the end, returns a {@link Supplier} guarding the given {@code action}. - * The {@code action} is synchronous and is always executed on the original thread. + * @deprecated use {@link Guard} or {@link TypedGuard} and {@code adaptSupplier} */ + @Deprecated(forRemoval = true) static Builder> createSupplier(Supplier action) { - return FaultToleranceSpiAccess.get().newBuilder(ft -> ft.adaptSupplier(action)); + return SpiAccess.get().newBuilder(ft -> ft.adaptSupplier(action)); } /** - * Returns a builder that, at the end, returns a {@link Runnable} guarding the given {@code action}. - * The {@code action} is synchronous and is always executed on the original thread. + * @deprecated use {@link Guard} or {@link TypedGuard}; there's no direct support + * for guarding {@code Runnable}s, but adapting to {@code Supplier} by using + * {@code adaptSupplier} should be close enough */ + @Deprecated(forRemoval = true) static Builder createRunnable(Runnable action) { - return FaultToleranceSpiAccess.get().newBuilder(ft -> ft.adaptRunnable(action)); + return SpiAccess.get().newBuilder(ft -> ft.adaptRunnable(action)); } /** - * Returns a builder that, at the end, returns a {@link FaultTolerance} object representing a set of configured - * fault tolerance strategies. It can be used to execute synchronous actions using {@link #call(Callable)}, - * {@link #get(Supplier)} or {@link #run(Runnable)}. - *

- * This method usually has to be called with an explicitly provided type argument. For example: - * {@code FaultTolerance.<String>create()}. + * @deprecated use {@link Guard#create()} or {@link TypedGuard#create(Class)} + * or {@link TypedGuard#create(TypeLiteral)} */ + @Deprecated(forRemoval = true) static Builder> create() { - return FaultToleranceSpiAccess.get().newBuilder(Function.identity()); + return SpiAccess.get().newBuilder(Function.identity()); } /** - * Returns a builder that, at the end, returns a {@link Callable} guarding the given {@code action}. - * The {@code action} is asynchronous and may be offloaded to another thread. + * @deprecated use {@link Guard} or {@link TypedGuard} and {@code adaptCallable} */ + @Deprecated(forRemoval = true) static Builder, Callable>> createAsyncCallable( Callable> action) { - return FaultToleranceSpiAccess.get().newAsyncBuilder(CompletionStage.class, ft -> ft.adaptCallable(action)); + return SpiAccess.get().newAsyncBuilder(CompletionStage.class, ft -> ft.adaptCallable(action)); } /** - * Returns a builder that, at the end, returns a {@link Supplier} guarding the given {@code action}. - * The {@code action} is asynchronous and may be offloaded to another thread. + * @deprecated use {@link Guard} or {@link TypedGuard} and {@code adaptSupplier} */ + @Deprecated(forRemoval = true) static Builder, Supplier>> createAsyncSupplier( Supplier> action) { - return FaultToleranceSpiAccess.get().newAsyncBuilder(CompletionStage.class, ft -> ft.adaptSupplier(action)); + return SpiAccess.get().newAsyncBuilder(CompletionStage.class, ft -> ft.adaptSupplier(action)); } /** - * Returns a builder that, at the end, returns a {@link Runnable} guarding the given {@code action}. - * The {@code action} is asynchronous and may be offloaded to another thread. + * @deprecated use {@link Guard} or {@link TypedGuard}; there's no direct support + * for guarding {@code Runnable}s, but adapting to {@code Supplier} by using + * {@code adaptSupplier} should be close enough */ + @Deprecated(forRemoval = true) static Builder, Runnable> createAsyncRunnable(Runnable action) { - return FaultToleranceSpiAccess.get().newAsyncBuilder(CompletionStage.class, ft -> ft.adaptRunnable(action)); + return SpiAccess.get().newAsyncBuilder(CompletionStage.class, ft -> ft.adaptRunnable(action)); } /** - * Returns a builder that, at the end, returns a {@link FaultTolerance} object representing a set of configured - * fault tolerance strategies. It can be used to execute asynchronous actions using {@link #call(Callable)}, - * {@link #get(Supplier)} or {@link #run(Runnable)}. - *

- * This method usually has to be called with an explicitly provided type argument. For example: - * {@code FaultTolerance.<String>createAsync()}. + * @deprecated use {@link Guard#create()} or {@link TypedGuard#create(Class)} + * or {@link TypedGuard#create(TypeLiteral)} */ + @Deprecated(forRemoval = true) static Builder, FaultTolerance>> createAsync() { - return FaultToleranceSpiAccess.get().newAsyncBuilder(CompletionStage.class, Function.identity()); + return SpiAccess.get().newAsyncBuilder(CompletionStage.class, Function.identity()); } /** - * Calls given {@code action} and guards the call by this configured set of fault tolerance strategies. - * If this {@code FaultTolerance} instance was created using {@link #create()}, the action is synchronous - * and is always executed on the same thread that calls this method. If this {@code FaultTolerance} instance - * was created using {@link #createAsync()}, the action is asynchronous and may be offloaded to another thread - * depending on how the builder was configured. + * @deprecated use {@link Guard#call(Callable, Class)} or {@link Guard#call(Callable, TypeLiteral)} + * or {@link TypedGuard#call(Callable)} */ + @Deprecated(forRemoval = true) T call(Callable action) throws Exception; /** - * Calls given {@code action} and guards the call by this configured set of fault tolerance strategies. - * If this {@code FaultTolerance} instance was created using {@code create*}, the action is synchronous - * and is always executed on the same thread that calls this method. If this {@code FaultTolerance} instance - * was created using {@code createAsync*}, the action is asynchronous and may be offloaded to another thread - * depending on how the builder was configured. + * @deprecated use {@link Guard#get(Supplier, Class)} or {@link Guard#get(Supplier, TypeLiteral)} + * or {@link TypedGuard#get(Supplier)} */ + @Deprecated(forRemoval = true) default T get(Supplier action) { try { return call(action::get); @@ -170,12 +125,10 @@ default T get(Supplier action) { } /** - * Calls given {@code action} and guards the call by this configured set of fault tolerance strategies. - * If this {@code FaultTolerance} instance was created using {@link #create()}, the action is synchronous - * and is always executed on the same thread that calls this method. If this {@code FaultTolerance} instance - * was created using {@link #createAsync()}, the action is asynchronous and may be offloaded to another thread - * depending on how the builder was configured. + * @deprecated use {@link Guard#get(Supplier, Class)} or {@link Guard#get(Supplier, TypeLiteral)} + * or {@link TypedGuard#get(Supplier)} */ + @Deprecated(forRemoval = true) default void run(Runnable action) { try { call(() -> { @@ -190,909 +143,359 @@ default void run(Runnable action) { } /** - * Adapts given {@code action} to an action guarded by this configured set of fault tolerance strategies. - * Useful when the action has to be called multiple times. - *

- * Equivalent to {@code () -> call(action)}. - * - * @see #call(Callable) + * @deprecated use {@link Guard#adaptCallable(Callable, Class)} or {@link Guard#adaptCallable(Callable, TypeLiteral)} + * or {@link TypedGuard#adaptCallable(Callable)} */ + @Deprecated(forRemoval = true) default Callable adaptCallable(Callable action) { return () -> call(action); } /** - * Adapts given {@code action} to an action guarded by this configured set of fault tolerance strategies. - * Useful when the action has to be called multiple times. - *

- * Equivalent to {@code () -> get(action)}. - * - * @see #get(Supplier) + * @deprecated use {@link Guard#adaptSupplier(Supplier, Class)} or {@link Guard#adaptSupplier(Supplier, TypeLiteral)} + * or {@link TypedGuard#adaptSupplier(Supplier)} */ + @Deprecated(forRemoval = true) default Supplier adaptSupplier(Supplier action) { return () -> get(action); } /** - * Adapts given {@code action} to an action guarded by this configured set of fault tolerance strategies. - * Useful when the action has to be called multiple times. - *

- * Equivalent to {@code () -> run(action)}. - * - * @see #run(Runnable) + * @deprecated use {@link Guard#adaptSupplier(Supplier, Class)} or {@link Guard#adaptSupplier(Supplier, TypeLiteral)} + * or {@link TypedGuard#adaptSupplier(Supplier)} */ + @Deprecated(forRemoval = true) default Runnable adaptRunnable(Runnable action) { return () -> run(action); } /** - * Casts this {@link FaultTolerance} object so that it guards actions of a different type. - * Since the type of the action is only used in fallback, this is usually safe; if this - * {@code FaultTolerance} object contains a fallback, this method throws an exception. - *

- * Note that this method may only be used to cast synchronous {@code FaultTolerance}. - * If this {@code FaultTolerance} object guards asynchronous actions, this method throws - * an exception. - * - * @param type of value of the guarded action + * @deprecated use {@link Guard} or {@link TypedGuard}, which unifies synchronous and asynchronous invocations */ + @Deprecated(forRemoval = true) FaultTolerance cast(); /** - * Casts this {@link FaultTolerance} object so that it guards actions of a different type. - * Since the type of the action is only used in fallback, this is usually safe; if this - * {@code FaultTolerance} object contains a fallback, this method throws an exception. - *

- * Note that this method may only be used to cast asynchronous {@code FaultTolerance} - * of given type (such as {@code CompletionStage} or {@code Uni}). If this {@code FaultTolerance} - * object guards synchronous actions or asynchronous actions of different type, this method - * throws an exception. - * - * @param asyncType the asynchronous type, such as {@code CompletionStage} or {@code Uni} - * @param type of value of the guarded action + * @deprecated use {@link Guard} or {@link TypedGuard}, which unifies synchronous and asynchronous invocations */ + @Deprecated(forRemoval = true) FaultTolerance castAsync(Class asyncType); /** - * A builder for configuring fault tolerance strategies. A fault tolerance strategy is included in the resulting - * set if the corresponding {@code with[Strategy]} method is called. Each strategy has its own builder to configure - * the necessary attributes, and each such builder has a {@code done()} method that returns back to this builder. - *

- * In general, all builders in this API accept multiple invocations of the same method, but only the last - * invocation is meaningful. Any previous invocations are forgotten. - * - * @param type of value of the guarded action - * @param type of result of this builder, depends on how this builder was created + * @deprecated use {@link Guard.Builder} or {@link TypedGuard.Builder} */ + @Deprecated(forRemoval = true) interface Builder { - /** - * Assigns a description to the resulting set of configured fault tolerance strategies. The description - * is used in logging messages and exception messages, and also as an identifier for metrics. - *

- * The description may be an arbitrary string. Duplicates are permitted. - *

- * If no description is set, a random UUID is used. - * - * @param value a description, must not be {@code null} - * @return this fault tolerance builder - */ + @Deprecated(forRemoval = true) Builder withDescription(String value); - /** - * Adds a bulkhead strategy. In this API, bulkhead is a simple concurrency limiter. - * - * @return a builder to configure the bulkhead strategy - * @see Bulkhead @Bulkhead - */ + @Deprecated(forRemoval = true) BulkheadBuilder withBulkhead(); - /** - * Adds a circuit breaker strategy. - * - * @return a builder to configure the circuit breaker strategy - * @see CircuitBreaker @CircuitBreaker - */ + @Deprecated(forRemoval = true) CircuitBreakerBuilder withCircuitBreaker(); - /** - * Adds a fallback strategy. - * - * @return a builder to configure the fallback strategy - * @see Fallback @Fallback - */ + @Deprecated(forRemoval = true) FallbackBuilder withFallback(); - /** - * Adds a rate limit strategy. - * - * @return a builder to configure the rate limit strategy - * @see RateLimit @RateLimit - */ + @Deprecated(forRemoval = true) RateLimitBuilder withRateLimit(); - /** - * Adds a retry strategy. Retry uses constant backoff between attempts by default, - * but may be configured to use exponential backoff, Fibonacci backoff, or custom backoff. - * - * @return a builder to configure the retry strategy - * @see Retry @Retry - */ + @Deprecated(forRemoval = true) RetryBuilder withRetry(); - /** - * Adds a timeout strategy. - * - * @return a builder to configure the timeout strategy - * @see Timeout @Timeout - */ + @Deprecated(forRemoval = true) TimeoutBuilder withTimeout(); - /** - * Configures whether the guarded action should be offloaded to another thread. Thread offload is - * only possible for asynchronous actions. If this builder was not created using {@code createAsync}, - * attempting to enable thread offload throws an exception. - * - * @param value whether the guarded action should be offloaded to another thread - * @return this fault tolerance builder - * @see Asynchronous @Asynchronous - * @see AsynchronousNonBlocking @AsynchronousNonBlocking - */ + @Deprecated(forRemoval = true) Builder withThreadOffload(boolean value); - /** - * Configures the executor to use when offloading the guarded action to another thread. Thread - * offload is only possible for asynchronous actions. If this builder was not created using - * {@code createAsync}, this method throws an exception. - *

- * If this method is not called, the guarded action is offloaded to the default executor - * provided by the integrator. - * - * @param executor the executor to which the guarded action should be offloaded - * @return this fault tolerance builder - */ + @Deprecated(forRemoval = true) Builder withThreadOffloadExecutor(Executor executor); - /** - * Returns a ready-to-use instance of {@code FaultTolerance} or guarded {@code Callable}, depending on - * how this builder was created. - */ + @Deprecated(forRemoval = true) R build(); - /** - * Syntactic sugar for calling the builder methods conditionally without breaking the invocation chain. - * For example: - * - * - *

{@code
-         * FaultTolerance.create(this::doSomething)
-         *     .withFallback() ... .done()
-         *     .with(builder -> {
-         *         if (useTimeout) {
-         *             builder.withTimeout() ... .done();
-         *         }
-         *     })
-         *     .build();
-         * }
- * - * - * @param consumer block of code to execute with this builder - * @return this fault tolerance builder - */ + @Deprecated(forRemoval = true) default Builder with(Consumer> consumer) { consumer.accept(this); return this; } - /** - * Configures a bulkhead. - * - * @see Bulkhead @Bulkhead - */ + @Deprecated(forRemoval = true) interface BulkheadBuilder { - /** - * Sets the concurrency limit the bulkhead will enforce. Defaults to 10. - * - * @param value the concurrency limit, must be >= 1 - * @return this bulkhead builder - */ + @Deprecated(forRemoval = true) BulkheadBuilder limit(int value); - /** - * Sets the maximum size of the bulkhead queue. Defaults to 10. - *

- * May only be called if the builder configures fault tolerance for asynchronous actions. In other words, - * if the builder was not created using {@code createAsync}, this method throws an exception. - * - * @param value the queue size, must be >= 1 - * @return this bulkhead builder - */ + @Deprecated(forRemoval = true) BulkheadBuilder queueSize(int value); /** - * Enables bulkhead queueing for synchronous actions executed on virtual threads. - * This makes it possible to call {@link #queueSize(int)} even if this builder does - * not configure fault tolerance for asynchronous actions. - *

- * If you use this method, you have to ensure that the guard - * is executed on a virtual thread. - * - * @return this bulkhead builder + * @deprecated use {@code enableSynchronousQueueing()} */ + @Deprecated(forRemoval = true) BulkheadBuilder enableVirtualThreadsQueueing(); - /** - * Sets a callback that will be invoked when this bulkhead accepts an invocation. - * In case of asynchronous actions, accepting into bulkhead doesn't mean the action - * is immediately invoked; the invocation is first put into a queue and may wait there. - *

- * The callback must be fast and non-blocking and must not throw an exception. - * - * @param callback the accepted callback, must not be {@code null} - * @return this bulkhead builder - */ + @Deprecated(forRemoval = true) BulkheadBuilder onAccepted(Runnable callback); - /** - * Sets a callback that will be invoked when this bulkhead rejects an invocation. - *

- * The callback must be fast and non-blocking and must not throw an exception. - * - * @param callback the rejected callback, must not be {@code null} - * @return this bulkhead builder - */ + @Deprecated(forRemoval = true) BulkheadBuilder onRejected(Runnable callback); - /** - * Sets a callback that will be invoked when a finished invocation leaves this bulkhead. - *

- * The callback must be fast and non-blocking and must not throw an exception. - * - * @param callback the finished callback, must not be {@code null} - * @return this bulkhead builder - */ + @Deprecated(forRemoval = true) BulkheadBuilder onFinished(Runnable callback); - /** - * Returns the original fault tolerance builder. - * - * @return the original fault tolerance builder - */ + @Deprecated(forRemoval = true) Builder done(); + @Deprecated(forRemoval = true) default BulkheadBuilder with(Consumer> consumer) { consumer.accept(this); return this; } } - /** - * Configures a circuit breaker. - * - * @see CircuitBreaker @CircuitBreaker - */ + @Deprecated(forRemoval = true) interface CircuitBreakerBuilder { - /** - * Sets the set of exception types considered failure. Defaults to all exceptions ({@code Throwable}). - * - * @param value collection of exception types, must not be {@code null} - * @return this circuit breaker builder - * @see CircuitBreaker#failOn() @CircuitBreaker.failOn - */ + @Deprecated(forRemoval = true) CircuitBreakerBuilder failOn(Collection> value); - /** - * Equivalent to {@link #failOn(Collection) failOn(Collections.singleton(value))}. - * - * @param value an exception class, must not be {@code null} - * @return this circuit breaker builder - */ + @Deprecated(forRemoval = true) default CircuitBreakerBuilder failOn(Class value) { return failOn(Collections.singleton(Objects.requireNonNull(value))); } - /** - * Sets the set of exception types considered success. Defaults to no exception (empty set). - * - * @param value collection of exception types, must not be {@code null} - * @return this circuit breaker builder - * @see CircuitBreaker#skipOn() @CircuitBreaker.skipOn - */ + @Deprecated(forRemoval = true) CircuitBreakerBuilder skipOn(Collection> value); - /** - * Equivalent to {@link #skipOn(Collection) skipOn(Collections.singleton(value))}. - * - * @param value an exception class, must not be {@code null} - * @return this circuit breaker builder - */ + @Deprecated(forRemoval = true) default CircuitBreakerBuilder skipOn(Class value) { return skipOn(Collections.singleton(Objects.requireNonNull(value))); } - /** - * Sets a predicate to determine when an exception should be considered failure - * by the circuit breaker. This is a more general variant of {@link #failOn(Collection) failOn}. - * Note that there is no generalized {@link #skipOn(Collection) skipOn}, because all exceptions - * that do not match this predicate are implicitly considered success. - *

- * If this method is called, {@code failOn} and {@code skipOn} may not be called. - * - * @param value the predicate, must not be {@code null} - * @return this circuit breaker builder - */ + @Deprecated(forRemoval = true) CircuitBreakerBuilder when(Predicate value); - /** - * Sets the delay after which an open circuit moves to half-open. Defaults to 5 seconds. - * - * @param value the delay length, must be >= 0 - * @param unit the delay unit, must not be {@code null} - * @return this circuit breaker builder - * @see CircuitBreaker#delay() @CircuitBreaker.delay - * @see CircuitBreaker#delayUnit() @CircuitBreaker.delayUnit - */ + @Deprecated(forRemoval = true) CircuitBreakerBuilder delay(long value, ChronoUnit unit); - /** - * Sets the size of the circuit breaker's rolling window. - * - * @param value the size of the circuit breaker's rolling window, must be >= 1 - * @return this circuit breaker builder - * @see CircuitBreaker#requestVolumeThreshold() @CircuitBreaker.requestVolumeThreshold - */ + @Deprecated(forRemoval = true) CircuitBreakerBuilder requestVolumeThreshold(int value); - /** - * Sets the failure ratio that, once reached, will move a closed circuit breaker to open. Defaults to 0.5. - * - * @param value the failure ratio, must be >= 0 and <= 1 - * @return this circuit breaker builder - * @see CircuitBreaker#failureRatio() @CircuitBreaker.failureRatio - */ + @Deprecated(forRemoval = true) CircuitBreakerBuilder failureRatio(double value); - /** - * Sets the number of successful executions that, once reached, will move a half-open circuit breaker - * to closed. Defaults to 1. - * - * @param value the number of successful executions, must be >= 1 - * @return this circuit breaker builder - * @see CircuitBreaker#successThreshold() @CircuitBreaker.successThreshold - */ + @Deprecated(forRemoval = true) CircuitBreakerBuilder successThreshold(int value); - /** - * Sets a circuit breaker name. Required to use the {@link CircuitBreakerMaintenance} methods. - * Defaults to unnamed. It is an error to use the same name for multiple circuit breakers. - *

- * If a circuit breaker is not given a name, its state will not be affected - * by {@link CircuitBreakerMaintenance#resetAll()}. This is unlike unnamed circuit breakers - * declared using {@code @CircuitBreaker}, because there's a fixed number of circuit breakers - * created using the declarative API, but a potentially unbounded number of circuit breakers - * created using the programmatic API. In other words, automatically remembering all - * circuit breakers created using the programmatic API would easily lead to a memory leak. - * - * @param value the circuit breaker name, must not be {@code null} - * @return this circuit breaker builder - * @see CircuitBreakerName @CircuitBreakerName - */ + @Deprecated(forRemoval = true) CircuitBreakerBuilder name(String value); - /** - * Sets a callback that will be invoked upon each state change of this circuit breaker. - *

- * The callback must be fast and non-blocking and must not throw an exception. - * - * @param callback the state change callback, must not be {@code null} - * @return this circuit breaker builder - */ + @Deprecated(forRemoval = true) CircuitBreakerBuilder onStateChange(Consumer callback); - /** - * Sets a callback that will be invoked when this circuit breaker treats a finished invocation as success. - *

- * The callback must be fast and non-blocking and must not throw an exception. - * - * @param callback the success callback, must not be {@code null} - * @return this circuit breaker builder - */ + @Deprecated(forRemoval = true) CircuitBreakerBuilder onSuccess(Runnable callback); - /** - * Sets a callback that will be invoked when this circuit breaker treats a finished invocation as failure. - *

- * The callback must be fast and non-blocking and must not throw an exception. - * - * @param callback the failure callback, must not be {@code null} - * @return this circuit breaker builder - */ + @Deprecated(forRemoval = true) CircuitBreakerBuilder onFailure(Runnable callback); - /** - * Sets a callback that will be invoked when this circuit breaker prevents an invocation, because it is - * in the open or half-open state. - *

- * The callback must be fast and non-blocking and must not throw an exception. - * - * @param callback the prevented callback, must not be {@code null} - * @return this circuit breaker builder - */ + @Deprecated(forRemoval = true) CircuitBreakerBuilder onPrevented(Runnable callback); - /** - * Returns the original fault tolerance builder. - * - * @return the original fault tolerance builder - */ + @Deprecated(forRemoval = true) Builder done(); + @Deprecated(forRemoval = true) default CircuitBreakerBuilder with(Consumer> consumer) { consumer.accept(this); return this; } } - /** - * Configures a fallback. - * - * @see Fallback @Fallback - */ + @Deprecated(forRemoval = true) interface FallbackBuilder { - /** - * Sets the fallback handler in the form of a fallback value {@link Supplier}. - * - * @param value the fallback value supplier, must not be {@code null} - * @return this fallback builder - */ + @Deprecated(forRemoval = true) FallbackBuilder handler(Supplier value); - /** - * Sets the fallback handler in the form of a {@link Function} that transforms the exception - * to the fallback value. - * - * @param value the fallback value function, must not be {@code null} - * @return this fallback builder - */ + @Deprecated(forRemoval = true) FallbackBuilder handler(Function value); - /** - * Sets the set of exception types considered failure. Defaults to all exceptions ({@code Throwable}). - * - * @param value collection of exception types, must not be {@code null} - * @return this fallback builder - * @see Fallback#applyOn() @Fallback.applyOn - */ + @Deprecated(forRemoval = true) FallbackBuilder applyOn(Collection> value); - /** - * Equivalent to {@link #applyOn(Collection) applyOn(Collections.singleton(value))}. - * - * @param value an exception class, must not be {@code null} - * @return this fallback builder - */ + @Deprecated(forRemoval = true) default FallbackBuilder applyOn(Class value) { return applyOn(Collections.singleton(Objects.requireNonNull(value))); } - /** - * Sets the set of exception types considered success. Defaults to no exception (empty set). - * - * @param value collection of exception types, must not be {@code null} - * @return this fallback builder - * @see Fallback#skipOn() @Fallback.skipOn - */ + @Deprecated(forRemoval = true) FallbackBuilder skipOn(Collection> value); - /** - * Equivalent to {@link #skipOn(Collection) skipOn(Collections.singleton(value))}. - * - * @param value an exception class, must not be {@code null} - * @return this fallback builder - */ + @Deprecated(forRemoval = true) default FallbackBuilder skipOn(Class value) { return skipOn(Collections.singleton(Objects.requireNonNull(value))); } - /** - * Sets a predicate to determine when an exception should be considered failure - * and fallback should be applied. This is a more general variant of {@link #applyOn(Collection) applyOn}. - * Note that there is no generalized {@link #skipOn(Collection) skipOn}, because all exceptions - * that do not match this predicate are implicitly considered success. - *

- * If this method is called, {@code applyOn} and {@code skipOn} may not be called. - * - * @param value the predicate, must not be {@code null} - * @return this fallback builder - */ + @Deprecated(forRemoval = true) FallbackBuilder when(Predicate value); - /** - * Returns the original fault tolerance builder. - * - * @return the original fault tolerance builder - */ + @Deprecated(forRemoval = true) Builder done(); + @Deprecated(forRemoval = true) default FallbackBuilder with(Consumer> consumer) { consumer.accept(this); return this; } } - /** - * Configures a rate limit. - * - * @see RateLimit @RateLimit - */ + @Deprecated(forRemoval = true) interface RateLimitBuilder { - /** - * Sets the maximum number of invocations in a time window. Defaults to 100. - * - * @param value maximum number of invocations in a time window, must be >= 1 - * @return this rate limit builder - */ + @Deprecated(forRemoval = true) RateLimitBuilder limit(int value); - /** - * Sets the time window length. Defaults to 1 second. - * - * @param value the time window size, must be >= 1 - * @return this rate limit builder - */ + @Deprecated(forRemoval = true) RateLimitBuilder window(long value, ChronoUnit unit); - /** - * Sets the minimum spacing between invocations. Defaults to 0. - * - * @param value the minimum spacing, must be >= 0 - * @return this rate limit builder - */ + @Deprecated(forRemoval = true) RateLimitBuilder minSpacing(long value, ChronoUnit unit); - /** - * Sets the type of time windows used for rate limiting. Defaults to {@link RateLimitType#FIXED}. - * - * @param value the time window type, must not be {@code null} - * @return this rate limit builder - */ + @Deprecated(forRemoval = true) RateLimitBuilder type(RateLimitType value); - /** - * Sets a callback that will be invoked when this rate limit permits an invocation. - *

- * The callback must be fast and non-blocking and must not throw an exception. - * - * @param callback the permitted callback, must not be {@code null} - * @return this rate limit builder - */ + @Deprecated(forRemoval = true) RateLimitBuilder onPermitted(Runnable callback); - /** - * Sets a callback that will be invoked when this rate limit rejects an invocation. - *

- * The callback must be fast and non-blocking and must not throw an exception. - * - * @param callback the rejected callback, must not be {@code null} - * @return this rate limit builder - */ + @Deprecated(forRemoval = true) RateLimitBuilder onRejected(Runnable callback); - /** - * Returns the original fault tolerance builder. - * - * @return the original fault tolerance builder - */ + @Deprecated(forRemoval = true) Builder done(); + @Deprecated(forRemoval = true) default RateLimitBuilder with(Consumer> consumer) { consumer.accept(this); return this; } } - /** - * Configures a retry. - * - * @see Retry @Retry - */ + @Deprecated(forRemoval = true) interface RetryBuilder { - /** - * Sets the maximum number of retries. Defaults to 3. - * - * @param value the maximum number of retries, must be >= -1. - * @return this retry builder - * @see Retry#maxRetries() @Retry.maxRetries - */ + @Deprecated(forRemoval = true) RetryBuilder maxRetries(int value); - /** - * Sets the delay between retries. Defaults to 0. - * - * @param value the delay length, must be >= 0 - * @param unit the delay unit, must not be {@code null} - * @return this retry builder - * @see Retry#delay() @Retry.delay - * @see Retry#delayUnit() @Retry.delayUnit - */ + @Deprecated(forRemoval = true) RetryBuilder delay(long value, ChronoUnit unit); - /** - * Sets the maximum duration of all invocations, including possible retries. Defaults to 3 minutes. - * - * @param value the maximum duration length, must be >= 0 - * @param unit the maximum duration unit, must not be {@code null} - * @return this retry builder - * @see Retry#maxDuration() @Retry.maxDuration - * @see Retry#durationUnit() @Retry.durationUnit - */ + @Deprecated(forRemoval = true) RetryBuilder maxDuration(long value, ChronoUnit unit); - /** - * Sets the jitter bound. Random value in the range from {@code -jitter} to {@code +jitter} will be added - * to the delay between retry attempts. Defaults to 200 millis. - * - * @param value the jitter bound length, must be >= 0 - * @param unit the jitter bound unit, must not be {@code null} - * @return this retry builder - * @see Retry#jitter() @Retry.jitter - * @see Retry#jitterDelayUnit() @Retry.jitterDelayUnit - */ + @Deprecated(forRemoval = true) RetryBuilder jitter(long value, ChronoUnit unit); - /** - * Sets the set of exception types considered failure. Defaults to all exceptions ({@code Exception}). - * - * @param value collection of exception types, must not be {@code null} - * @return this retry builder - * @see Retry#retryOn() @Retry.retryOn - */ + @Deprecated(forRemoval = true) RetryBuilder retryOn(Collection> value); - /** - * Equivalent to {@link #retryOn(Collection) retryOn(Collections.singleton(value))}. - * - * @param value an exception class, must not be {@code null} - * @return this retry builder - */ + @Deprecated(forRemoval = true) default RetryBuilder retryOn(Class value) { return retryOn(Collections.singleton(Objects.requireNonNull(value))); } - /** - * Sets the set of exception types considered success. Defaults to no exception (empty set). - * - * @param value collection of exception types, must not be {@code null} - * @return this retry builder - * @see Retry#abortOn() @Retry.abortOn - */ + @Deprecated(forRemoval = true) RetryBuilder abortOn(Collection> value); - /** - * Equivalent to {@link #abortOn(Collection) abortOn(Collections.singleton(value))}. - * - * @param value an exception class, must not be {@code null} - * @return this retry builder - */ + @Deprecated(forRemoval = true) default RetryBuilder abortOn(Class value) { return abortOn(Collections.singleton(Objects.requireNonNull(value))); } - /** - * Sets a predicate to determine when a result should be considered failure and retry - * should be attempted. All results that do not match this predicate are implicitly - * considered success. - * - * @param value the predicate, must not be {@code null} - * @return this retry builder - */ + @Deprecated(forRemoval = true) RetryBuilder whenResult(Predicate value); - /** - * @deprecated use {@link #whenException(Predicate)} - */ @Deprecated(forRemoval = true) default RetryBuilder when(Predicate value) { return whenException(value); } - /** - * Sets a predicate to determine when an exception should be considered failure - * and retry should be attempted. This is a more general variant of {@link #retryOn(Collection) retryOn}. - * Note that there is no generalized {@link #abortOn(Collection) abortOn}, because all exceptions - * that do not match this predicate are implicitly considered success. - *

- * If this method is called, {@code retryOn} and {@code abortOn} may not be called. - * - * @param value the predicate, must not be {@code null} - * @return this retry builder - */ + @Deprecated(forRemoval = true) RetryBuilder whenException(Predicate value); - /** - * Sets a before retry handler, which is called before each retry, but not before the original attempt. - * - * @param value the before retry handler, must not be {@code null} - * @return this retry builder - */ + @Deprecated(forRemoval = true) RetryBuilder beforeRetry(Runnable value); - /** - * Sets a before retry handler, which is called before each retry, but not before the original attempt. - * - * @param value the before retry handler, must not be {@code null} - * @return this retry builder - */ + @Deprecated(forRemoval = true) RetryBuilder beforeRetry(Consumer value); - /** - * Configures retry to use an exponential backoff instead of the default constant backoff. - *

- * Only one backoff strategy may be configured, so calling {@link #withFibonacciBackoff()} - * or {@link #withCustomBackoff()} in addition to this method leads to an exception - * during {@link #done()}. - * - * @return the exponential backoff builder - * @see ExponentialBackoff @ExponentialBackoff - */ + @Deprecated(forRemoval = true) ExponentialBackoffBuilder withExponentialBackoff(); - /** - * Configures retry to use a Fibonacci backoff instead of the default constant backoff. - *

- * Only one backoff strategy may be configured, so calling {@link #withExponentialBackoff()} - * or {@link #withCustomBackoff()} in addition to this method leads to an exception - * during {@link #done()}. - * - * @return the Fibonacci backoff builder - * @see FibonacciBackoff @FibonacciBackoff - */ + @Deprecated(forRemoval = true) FibonacciBackoffBuilder withFibonacciBackoff(); - /** - * Configures retry to use a custom backoff instead of the default constant backoff. - *

- * Only one backoff strategy may be configured, so calling {@link #withExponentialBackoff()} - * or {@link #withFibonacciBackoff()} in addition to this method leads to an exception - * during {@link #done()}. - * - * @return the custom backoff builder - * @see CustomBackoff @CustomBackoff - */ + @Deprecated(forRemoval = true) CustomBackoffBuilder withCustomBackoff(); - /** - * Sets a callback that will be invoked when a retry is attempted. - *

- * The callback must be fast and non-blocking and must not throw an exception. - * - * @param callback the retried callback, must not be {@code null} - * @return this retry builder - */ + @Deprecated(forRemoval = true) RetryBuilder onRetry(Runnable callback); - /** - * Sets a callback that will be invoked when this retry strategy treats a finished invocation as success, - * regardless of whether a retry was attempted or not. - *

- * The callback must be fast and non-blocking and must not throw an exception. - * - * @param callback the retried callback, must not be {@code null} - * @return this retry builder - */ + @Deprecated(forRemoval = true) RetryBuilder onSuccess(Runnable callback); - /** - * Sets a callback that will be invoked when this retry strategy treats a finished invocation as failure, - * and no more retries will be attempted. The failure may be caused by depleting the maximum - * number of retries or the maximum duration, or by an exception that is not retryable. - *

- * The callback must be fast and non-blocking and must not throw an exception. - * - * @param callback the retried callback, must not be {@code null} - * @return this retry builder - */ + @Deprecated(forRemoval = true) RetryBuilder onFailure(Runnable callback); - /** - * Returns the original fault tolerance builder. - * - * @return the original fault tolerance builder - */ + @Deprecated(forRemoval = true) Builder done(); + @Deprecated(forRemoval = true) default RetryBuilder with(Consumer> consumer) { consumer.accept(this); return this; } - /** - * Configures an exponential backoff for retry. - * - * @see ExponentialBackoff @ExponentialBackoff - */ + @Deprecated(forRemoval = true) interface ExponentialBackoffBuilder { - /** - * Sets the multiplicative factor used to determine delay between retries. Defaults to 2. - * - * @param value the multiplicative factor, must be >= 1 - * @return this exponential backoff builder - * @see ExponentialBackoff#factor() @ExponentialBackoff.factor - */ + @Deprecated(forRemoval = true) ExponentialBackoffBuilder factor(int value); - /** - * Sets the maximum delay between retries. Defaults to 1 minute. - * - * @param value the maximum delay, must be >= 0 - * @param unit the maximum delay unit, must not be {@code null} - * @return this exponential backoff builder - * @see ExponentialBackoff#maxDelay() @ExponentialBackoff.maxDelay - * @see ExponentialBackoff#maxDelayUnit() @ExponentialBackoff.maxDelayUnit - */ + @Deprecated(forRemoval = true) ExponentialBackoffBuilder maxDelay(long value, ChronoUnit unit); - /** - * Returns the original retry builder. - * - * @return the original retry builder - */ + @Deprecated(forRemoval = true) RetryBuilder done(); + @Deprecated(forRemoval = true) default ExponentialBackoffBuilder with(Consumer> consumer) { consumer.accept(this); return this; } } - /** - * Configures a Fibonacci backoff for retry. - * - * @see FibonacciBackoff @FibonacciBackoff - */ + @Deprecated(forRemoval = true) interface FibonacciBackoffBuilder { - /** - * Sets the maximum delay between retries. Defaults to 1 minute. - * - * @param value the maximum delay, must be >= 0 - * @param unit the maximum delay unit, must not be {@code null} - * @return this fibonacci backoff builder - * @see FibonacciBackoff#maxDelay() @FibonacciBackoff.maxDelay - * @see FibonacciBackoff#maxDelayUnit() @FibonacciBackoff.maxDelayUnit - */ + @Deprecated(forRemoval = true) FibonacciBackoffBuilder maxDelay(long value, ChronoUnit unit); - /** - * Returns the original retry builder. - * - * @return the original retry builder - */ + @Deprecated(forRemoval = true) RetryBuilder done(); + @Deprecated(forRemoval = true) default FibonacciBackoffBuilder with(Consumer> consumer) { consumer.accept(this); return this; } } - /** - * Configures a custom backoff for retry. - * - * @see CustomBackoff @CustomBackoff - */ + @Deprecated(forRemoval = true) interface CustomBackoffBuilder { - /** - * Sets the custom backoff strategy in the form of a {@link Supplier} of {@link CustomBackoffStrategy} - * instances. Mandatory. - * - * @see CustomBackoff#value() - */ + @Deprecated(forRemoval = true) CustomBackoffBuilder strategy(Supplier value); - /** - * Returns the original retry builder. - */ + @Deprecated(forRemoval = true) RetryBuilder done(); + @Deprecated(forRemoval = true) default CustomBackoffBuilder with(Consumer> consumer) { consumer.accept(this); return this; @@ -1100,50 +503,21 @@ default CustomBackoffBuilder with(Consumer> con } } - /** - * Configures a timeout. - * - * @see Timeout @Timeout - */ + @Deprecated(forRemoval = true) interface TimeoutBuilder { - /** - * Sets the timeout duration. Defaults to 1 second. - * - * @param value the timeout length, must be >= 0 - * @param unit the timeout unit, must not be {@code null} - * @return this timeout builder - * @see Timeout#value() @Timeout.value - * @see Timeout#unit() @Timeout.unit - */ + @Deprecated(forRemoval = true) TimeoutBuilder duration(long value, ChronoUnit unit); - /** - * Sets a callback that will be invoked when an invocation times out. - *

- * The callback must be fast and non-blocking and must not throw an exception. - * - * @param callback the timeout callback, must not be {@code null} - * @return this timeout builder - */ + @Deprecated(forRemoval = true) TimeoutBuilder onTimeout(Runnable callback); - /** - * Sets a callback that will be invoked when an invocation finishes before the timeout. - *

- * The callback must be fast and non-blocking and must not throw an exception. - * - * @param callback the finished callback, must not be {@code null} - * @return this timeout builder - */ + @Deprecated(forRemoval = true) TimeoutBuilder onFinished(Runnable callback); - /** - * Returns the original fault tolerance builder. - * - * @return the original fault tolerance builder - */ + @Deprecated(forRemoval = true) Builder done(); + @Deprecated(forRemoval = true) default TimeoutBuilder with(Consumer> consumer) { consumer.accept(this); return this; diff --git a/api/src/main/java/io/smallrye/faulttolerance/api/Guard.java b/api/src/main/java/io/smallrye/faulttolerance/api/Guard.java new file mode 100644 index 00000000..e42d9d11 --- /dev/null +++ b/api/src/main/java/io/smallrye/faulttolerance/api/Guard.java @@ -0,0 +1,946 @@ +package io.smallrye.faulttolerance.api; + +import java.time.temporal.ChronoUnit; +import java.util.Collection; +import java.util.Collections; +import java.util.Objects; +import java.util.concurrent.Callable; +import java.util.concurrent.Executor; +import java.util.function.Consumer; +import java.util.function.Predicate; +import java.util.function.Supplier; + +import jakarta.enterprise.util.TypeLiteral; + +import org.eclipse.microprofile.faulttolerance.Asynchronous; +import org.eclipse.microprofile.faulttolerance.Bulkhead; +import org.eclipse.microprofile.faulttolerance.CircuitBreaker; +import org.eclipse.microprofile.faulttolerance.Retry; +import org.eclipse.microprofile.faulttolerance.Timeout; + +import io.smallrye.common.annotation.Experimental; + +/** + * Allows guarding an action with various fault tolerance strategies: bulkhead, circuit breaker, rate limit, retry. + * and timeout. Synchronous as well as asynchronous actions may be guarded, asynchronous actions may optionally + * be offloaded to another thread. This interface doesn't support fallback; for that, use {@link TypedGuard}. + *

+ * An instance of this interface represents a configured set of fault tolerance strategies. It can be used to + * guard a {@link #call(Callable, Class) Callable} or {@link #get(Supplier, Class) Supplier} invocation, or adapt + * an unguarded {@link #adaptCallable(Callable, Class) Callable} or {@link #adaptSupplier(Supplier, Class) Supplier} + * to a guarded one. + *

+ * The {@link #create()} method return a builder that allows configuring the supported fault tolerance strategies. + * Order of builder method invocations does not matter, the fault tolerance strategies are always applied + * in a predefined order: retry > circuit breaker > rate limit > timeout > bulkhead > thread offload + * > guarded action. + *

+ * Note that bulkheads, circuit breakers and rate limits are stateful, so there's a big difference between guarding + * multiple actions using the same {@code Guard} object and using a separate {@code Guard} object for each action. + * Using a single {@code Guard} instance to guard multiple actions means that a single bulkhead, circuit breaker + * and/or rate limit will be shared among all those actions. + *

+ * This API is essentially a programmatic equivalent to the declarative, annotation-based API of MicroProfile Fault + * Tolerance and SmallRye Fault Tolerance. It shares the set of fault tolerance strategies, their invocation order + * and behavior, their configuration properties, etc. Notable differences are: + *

    + *
  • asynchronous actions of type {@link java.util.concurrent.Future} are not supported;
  • + *
  • the circuit breaker and retry strategies always inspect the cause chain of exceptions, + * following the behavior of SmallRye Fault Tolerance in the non-compatible mode.
  • + *
+ */ +@Experimental("second attempt at providing programmatic API") +public interface Guard { + /** + * Creates a builder for producing a {@link Guard} object representing a set of configured fault tolerance + * strategies. It can be used to execute actions using {@link #call(Callable, Class) call()} or + * {@link #get(Supplier, Class) get()}. + */ + static Builder create() { + return SpiAccess.get().newGuardBuilder(); + } + + /** + * Calls given {@code action}, which returns given {@code type}, and guards the call by this configured set + * of fault tolerance strategies. + *

+ * The given {@code type} is used to determine if the {@code action} is synchronous or asynchronous. + * If the {@code action} is synchronous, it is always executed on the same thread that calls this method. + * If the {@code action} is asynchronous, it may be offloaded to another thread depending on how the builder + * was configured. + */ + T call(Callable action, Class type) throws Exception; + + /** + * Calls given {@code action}, which returns given {@code type}, and guards the call by this configured set + * of fault tolerance strategies. + *

+ * The given {@code type} is used to determine if the {@code action} is synchronous or asynchronous. + * If the {@code action} is synchronous, it is always executed on the same thread that calls this method. + * If the {@code action} is asynchronous, it may be offloaded to another thread depending on how the builder + * was configured. + */ + T call(Callable action, TypeLiteral type) throws Exception; + + /** + * Calls given {@code action}, which returns given {@code type}, and guards the call by this configured set + * of fault tolerance strategies. + *

+ * The given {@code type} is used to determine if the {@code action} is synchronous or asynchronous. + * If the {@code action} is synchronous, it is always executed on the same thread that calls this method. + * If the {@code action} is asynchronous, it may be offloaded to another thread depending on how the builder + * was configured. + */ + T get(Supplier action, Class type); + + /** + * Calls given {@code action}, which returns given {@code type}, and guards the call by this configured set + * of fault tolerance strategies. + *

+ * The given {@code type} is used to determine if the {@code action} is synchronous or asynchronous. + * If the {@code action} is synchronous, it is always executed on the same thread that calls this method. + * If the {@code action} is asynchronous, it may be offloaded to another thread depending on how the builder + * was configured. + */ + T get(Supplier action, TypeLiteral type); + + /** + * Adapts given {@code action} to an action guarded by this configured set of fault tolerance strategies. + * Useful when the action has to be called multiple times. + *

+ * Equivalent to {@code () -> call(action, type)}. + * + * @see #call(Callable, Class) + */ + default Callable adaptCallable(Callable action, Class type) { + return () -> call(action, type); + } + + /** + * Adapts given {@code action} to an action guarded by this configured set of fault tolerance strategies. + * Useful when the action has to be called multiple times. + *

+ * Equivalent to {@code () -> call(action, type)}. + * + * @see #call(Callable, TypeLiteral) + */ + default Callable adaptCallable(Callable action, TypeLiteral type) { + return () -> call(action, type); + } + + /** + * Adapts given {@code action} to an action guarded by this configured set of fault tolerance strategies. + * Useful when the action has to be called multiple times. + *

+ * Equivalent to {@code () -> get(action, type)}. + * + * @see #get(Supplier, Class) + */ + default Supplier adaptSupplier(Supplier action, Class type) { + return () -> get(action, type); + } + + /** + * Adapts given {@code action} to an action guarded by this configured set of fault tolerance strategies. + * Useful when the action has to be called multiple times. + *

+ * Equivalent to {@code () -> get(action, type)}. + * + * @see #get(Supplier, TypeLiteral) + */ + default Supplier adaptSupplier(Supplier action, TypeLiteral type) { + return () -> get(action, type); + } + + /** + * A builder for configuring fault tolerance strategies. A fault tolerance strategy is included in the resulting + * set if the corresponding {@code with[Strategy]} method is called. Each strategy has its own builder to configure + * the necessary attributes, and each such builder has a {@code done()} method that returns back to this builder. + *

+ * In general, all builders in this API accept multiple invocations of the same method, but only the last + * invocation is meaningful. Any previous invocations are forgotten. + */ + interface Builder { + /** + * Assigns a description to the resulting set of configured fault tolerance strategies. The description + * is used in logging messages and exception messages, and also as an identifier for metrics. + *

+ * The description may be an arbitrary string. Duplicates are permitted. + *

+ * If no description is set, a random UUID is used. + * + * @param value a description, must not be {@code null} + * @return this fault tolerance builder + */ + Builder withDescription(String value); + + /** + * Adds a bulkhead strategy. In this API, bulkhead is a simple concurrency limiter. + * + * @return a builder to configure the bulkhead strategy + * @see Bulkhead @Bulkhead + */ + BulkheadBuilder withBulkhead(); + + /** + * Adds a circuit breaker strategy. + * + * @return a builder to configure the circuit breaker strategy + * @see CircuitBreaker @CircuitBreaker + */ + CircuitBreakerBuilder withCircuitBreaker(); + + /** + * Adds a rate limit strategy. + * + * @return a builder to configure the rate limit strategy + * @see RateLimit @RateLimit + */ + RateLimitBuilder withRateLimit(); + + /** + * Adds a retry strategy. Retry uses constant backoff between attempts by default, + * but may be configured to use exponential backoff, Fibonacci backoff, or custom backoff. + * + * @return a builder to configure the retry strategy + * @see Retry @Retry + */ + RetryBuilder withRetry(); + + /** + * Adds a timeout strategy. + * + * @return a builder to configure the timeout strategy + * @see Timeout @Timeout + */ + TimeoutBuilder withTimeout(); + + /** + * Configures whether an asynchronous guarded action should be offloaded to another thread. + * + * @param value whether an asynchronous guarded action should be offloaded to another thread + * @return this fault tolerance builder + * @see Asynchronous @Asynchronous + * @see AsynchronousNonBlocking @AsynchronousNonBlocking + */ + Builder withThreadOffload(boolean value); + + /** + * Configures the executor to use when offloading the guarded action to another thread. + *

+ * If this method is not called but thread offload is enabled using {@link #withThreadOffload(boolean)}, + * an asynchronous guarded action is offloaded to the default executor provided by the integrator. + * + * @param executor the executor to which the guarded action should be offloaded + * @return this fault tolerance builder + */ + Builder withThreadOffloadExecutor(Executor executor); + + /** + * Returns a ready-to-use instance of {@code Guard}. + */ + Guard build(); + + /** + * Syntactic sugar for calling the builder methods conditionally without breaking the invocation chain. + * For example: + * + * + *

{@code
+         * Guard.create()
+         *     .withRetry() ... .done()
+         *     .with(builder -> {
+         *         if (useTimeout) {
+         *             builder.withTimeout() ... .done();
+         *         }
+         *     })
+         *     .build();
+         * }
+ * + * + * @param consumer block of code to execute with this builder + * @return this fault tolerance builder + */ + default Builder with(Consumer consumer) { + consumer.accept(this); + return this; + } + + /** + * Configures a bulkhead. + * + * @see Bulkhead @Bulkhead + */ + interface BulkheadBuilder { + /** + * Sets the concurrency limit the bulkhead will enforce. Defaults to 10. + * + * @param value the concurrency limit, must be >= 1 + * @return this bulkhead builder + * @see Bulkhead#value() @Bulkhead.value + */ + BulkheadBuilder limit(int value); + + /** + * Sets the maximum size of the bulkhead queue. Defaults to 10. + * + * @param value the queue size, must be >= 1 + * @return this bulkhead builder + * @see Bulkhead#waitingTaskQueue() @Bulkhead.waitingTaskQueue + */ + BulkheadBuilder queueSize(int value); + + /** + * Enables bulkhead queueing for synchronous actions. + *

+ * If you use this method, you have to ensure that the guard + * is executed on a virtual thread. + * + * @return this bulkhead builder + */ + BulkheadBuilder enableSynchronousQueueing(); + + /** + * Sets a callback that will be invoked when this bulkhead accepts an invocation. + * In case of asynchronous actions, accepting into bulkhead doesn't mean the action + * is immediately invoked; the invocation is first put into a queue and may wait there. + *

+ * The callback must be fast and non-blocking and must not throw an exception. + * + * @param callback the accepted callback, must not be {@code null} + * @return this bulkhead builder + */ + BulkheadBuilder onAccepted(Runnable callback); + + /** + * Sets a callback that will be invoked when this bulkhead rejects an invocation. + *

+ * The callback must be fast and non-blocking and must not throw an exception. + * + * @param callback the rejected callback, must not be {@code null} + * @return this bulkhead builder + */ + BulkheadBuilder onRejected(Runnable callback); + + /** + * Sets a callback that will be invoked when a finished invocation leaves this bulkhead. + *

+ * The callback must be fast and non-blocking and must not throw an exception. + * + * @param callback the finished callback, must not be {@code null} + * @return this bulkhead builder + */ + BulkheadBuilder onFinished(Runnable callback); + + /** + * Returns the original fault tolerance builder. + * + * @return the original fault tolerance builder + */ + Builder done(); + + default BulkheadBuilder with(Consumer consumer) { + consumer.accept(this); + return this; + } + } + + /** + * Configures a circuit breaker. + * + * @see CircuitBreaker @CircuitBreaker + */ + interface CircuitBreakerBuilder { + /** + * Sets the set of exception types considered failure. Defaults to all exceptions ({@code Throwable}). + * + * @param value collection of exception types, must not be {@code null} + * @return this circuit breaker builder + * @see CircuitBreaker#failOn() @CircuitBreaker.failOn + */ + CircuitBreakerBuilder failOn(Collection> value); + + /** + * Equivalent to {@link #failOn(Collection) failOn(Collections.singleton(value))}. + * + * @param value an exception class, must not be {@code null} + * @return this circuit breaker builder + */ + default CircuitBreakerBuilder failOn(Class value) { + return failOn(Collections.singleton(Objects.requireNonNull(value))); + } + + /** + * Sets the set of exception types considered success. Defaults to no exception (empty set). + * + * @param value collection of exception types, must not be {@code null} + * @return this circuit breaker builder + * @see CircuitBreaker#skipOn() @CircuitBreaker.skipOn + */ + CircuitBreakerBuilder skipOn(Collection> value); + + /** + * Equivalent to {@link #skipOn(Collection) skipOn(Collections.singleton(value))}. + * + * @param value an exception class, must not be {@code null} + * @return this circuit breaker builder + */ + default CircuitBreakerBuilder skipOn(Class value) { + return skipOn(Collections.singleton(Objects.requireNonNull(value))); + } + + /** + * Sets a predicate to determine when an exception should be considered failure + * by the circuit breaker. This is a more general variant of {@link #failOn(Collection) failOn}. + * Note that there is no generalized {@link #skipOn(Collection) skipOn}, because all exceptions + * that do not match this predicate are implicitly considered success. + *

+ * If this method is called, {@code failOn} and {@code skipOn} may not be called. + * + * @param value the predicate, must not be {@code null} + * @return this circuit breaker builder + */ + CircuitBreakerBuilder when(Predicate value); + + /** + * Sets the delay after which an open circuit moves to half-open. Defaults to 5 seconds. + * + * @param value the delay length, must be >= 0 + * @param unit the delay unit, must not be {@code null} + * @return this circuit breaker builder + * @see CircuitBreaker#delay() @CircuitBreaker.delay + * @see CircuitBreaker#delayUnit() @CircuitBreaker.delayUnit + */ + CircuitBreakerBuilder delay(long value, ChronoUnit unit); + + /** + * Sets the size of the circuit breaker's rolling window. + * + * @param value the size of the circuit breaker's rolling window, must be >= 1 + * @return this circuit breaker builder + * @see CircuitBreaker#requestVolumeThreshold() @CircuitBreaker.requestVolumeThreshold + */ + CircuitBreakerBuilder requestVolumeThreshold(int value); + + /** + * Sets the failure ratio that, once reached, will move a closed circuit breaker to open. Defaults to 0.5. + * + * @param value the failure ratio, must be >= 0 and <= 1 + * @return this circuit breaker builder + * @see CircuitBreaker#failureRatio() @CircuitBreaker.failureRatio + */ + CircuitBreakerBuilder failureRatio(double value); + + /** + * Sets the number of successful executions that, once reached, will move a half-open circuit breaker + * to closed. Defaults to 1. + * + * @param value the number of successful executions, must be >= 1 + * @return this circuit breaker builder + * @see CircuitBreaker#successThreshold() @CircuitBreaker.successThreshold + */ + CircuitBreakerBuilder successThreshold(int value); + + /** + * Sets a circuit breaker name. Required to use the {@link CircuitBreakerMaintenance} methods. + * Defaults to unnamed. It is an error to use the same name for multiple circuit breakers. + *

+ * If a circuit breaker is not given a name, its state will not be affected + * by {@link CircuitBreakerMaintenance#resetAll()}. This is unlike unnamed circuit breakers + * declared using {@code @CircuitBreaker}, because there's a fixed number of circuit breakers + * created using the declarative API, but a potentially unbounded number of circuit breakers + * created using the programmatic API. In other words, automatically remembering all + * circuit breakers created using the programmatic API would easily lead to a memory leak. + * + * @param value the circuit breaker name, must not be {@code null} + * @return this circuit breaker builder + * @see CircuitBreakerName @CircuitBreakerName + */ + CircuitBreakerBuilder name(String value); + + /** + * Sets a callback that will be invoked upon each state change of this circuit breaker. + *

+ * The callback must be fast and non-blocking and must not throw an exception. + * + * @param callback the state change callback, must not be {@code null} + * @return this circuit breaker builder + */ + CircuitBreakerBuilder onStateChange(Consumer callback); + + /** + * Sets a callback that will be invoked when this circuit breaker treats a finished invocation as success. + *

+ * The callback must be fast and non-blocking and must not throw an exception. + * + * @param callback the success callback, must not be {@code null} + * @return this circuit breaker builder + */ + CircuitBreakerBuilder onSuccess(Runnable callback); + + /** + * Sets a callback that will be invoked when this circuit breaker treats a finished invocation as failure. + *

+ * The callback must be fast and non-blocking and must not throw an exception. + * + * @param callback the failure callback, must not be {@code null} + * @return this circuit breaker builder + */ + CircuitBreakerBuilder onFailure(Runnable callback); + + /** + * Sets a callback that will be invoked when this circuit breaker prevents an invocation, because it is + * in the open or half-open state. + *

+ * The callback must be fast and non-blocking and must not throw an exception. + * + * @param callback the prevented callback, must not be {@code null} + * @return this circuit breaker builder + */ + CircuitBreakerBuilder onPrevented(Runnable callback); + + /** + * Returns the original fault tolerance builder. + * + * @return the original fault tolerance builder + */ + Builder done(); + + default CircuitBreakerBuilder with(Consumer consumer) { + consumer.accept(this); + return this; + } + } + + /** + * Configures a rate limit. + * + * @see RateLimit @RateLimit + */ + interface RateLimitBuilder { + /** + * Sets the maximum number of invocations in a time window. Defaults to 100. + * + * @param value maximum number of invocations in a time window, must be >= 1 + * @return this rate limit builder + * @see RateLimit#value() @RateLimit.value + */ + RateLimitBuilder limit(int value); + + /** + * Sets the time window length. Defaults to 1 second. + * + * @param value the time window size, must be >= 1 + * @return this rate limit builder + * @see RateLimit#window() @RateLimit.window + * @see RateLimit#windowUnit() @RateLimit.windowUnit + */ + RateLimitBuilder window(long value, ChronoUnit unit); + + /** + * Sets the minimum spacing between invocations. Defaults to 0. + * + * @param value the minimum spacing, must be >= 0 + * @return this rate limit builder + * @see RateLimit#minSpacing() @RateLimit.minSpacing + * @see RateLimit#minSpacingUnit() @RateLimit.minSpacingUnit + */ + RateLimitBuilder minSpacing(long value, ChronoUnit unit); + + /** + * Sets the type of time windows used for rate limiting. Defaults to {@link RateLimitType#FIXED}. + * + * @param value the time window type, must not be {@code null} + * @return this rate limit builder + * @see RateLimit#type() @RateLimit.type + */ + RateLimitBuilder type(RateLimitType value); + + /** + * Sets a callback that will be invoked when this rate limit permits an invocation. + *

+ * The callback must be fast and non-blocking and must not throw an exception. + * + * @param callback the permitted callback, must not be {@code null} + * @return this rate limit builder + */ + RateLimitBuilder onPermitted(Runnable callback); + + /** + * Sets a callback that will be invoked when this rate limit rejects an invocation. + *

+ * The callback must be fast and non-blocking and must not throw an exception. + * + * @param callback the rejected callback, must not be {@code null} + * @return this rate limit builder + */ + RateLimitBuilder onRejected(Runnable callback); + + /** + * Returns the original fault tolerance builder. + * + * @return the original fault tolerance builder + */ + Builder done(); + + default RateLimitBuilder with(Consumer consumer) { + consumer.accept(this); + return this; + } + } + + /** + * Configures a retry. + * + * @see Retry @Retry + */ + interface RetryBuilder { + /** + * Sets the maximum number of retries. Defaults to 3. + * + * @param value the maximum number of retries, must be >= -1. + * @return this retry builder + * @see Retry#maxRetries() @Retry.maxRetries + */ + RetryBuilder maxRetries(int value); + + /** + * Sets the delay between retries. Defaults to 0. + * + * @param value the delay length, must be >= 0 + * @param unit the delay unit, must not be {@code null} + * @return this retry builder + * @see Retry#delay() @Retry.delay + * @see Retry#delayUnit() @Retry.delayUnit + */ + RetryBuilder delay(long value, ChronoUnit unit); + + /** + * Sets the maximum duration of all invocations, including possible retries. Defaults to 3 minutes. + * + * @param value the maximum duration length, must be >= 0 + * @param unit the maximum duration unit, must not be {@code null} + * @return this retry builder + * @see Retry#maxDuration() @Retry.maxDuration + * @see Retry#durationUnit() @Retry.durationUnit + */ + RetryBuilder maxDuration(long value, ChronoUnit unit); + + /** + * Sets the jitter bound. Random value in the range from {@code -jitter} to {@code +jitter} will be added + * to the delay between retry attempts. Defaults to 200 millis. + * + * @param value the jitter bound length, must be >= 0 + * @param unit the jitter bound unit, must not be {@code null} + * @return this retry builder + * @see Retry#jitter() @Retry.jitter + * @see Retry#jitterDelayUnit() @Retry.jitterDelayUnit + */ + RetryBuilder jitter(long value, ChronoUnit unit); + + /** + * Sets the set of exception types considered failure. Defaults to all exceptions ({@code Exception}). + * + * @param value collection of exception types, must not be {@code null} + * @return this retry builder + * @see Retry#retryOn() @Retry.retryOn + */ + RetryBuilder retryOn(Collection> value); + + /** + * Equivalent to {@link #retryOn(Collection) retryOn(Collections.singleton(value))}. + * + * @param value an exception class, must not be {@code null} + * @return this retry builder + */ + default RetryBuilder retryOn(Class value) { + return retryOn(Collections.singleton(Objects.requireNonNull(value))); + } + + /** + * Sets the set of exception types considered success. Defaults to no exception (empty set). + * + * @param value collection of exception types, must not be {@code null} + * @return this retry builder + * @see Retry#abortOn() @Retry.abortOn + */ + RetryBuilder abortOn(Collection> value); + + /** + * Equivalent to {@link #abortOn(Collection) abortOn(Collections.singleton(value))}. + * + * @param value an exception class, must not be {@code null} + * @return this retry builder + */ + default RetryBuilder abortOn(Class value) { + return abortOn(Collections.singleton(Objects.requireNonNull(value))); + } + + /** + * Sets a predicate to determine when a result should be considered failure and retry + * should be attempted. All results that do not match this predicate are implicitly + * considered success. + * + * @param value the predicate, must not be {@code null} + * @return this retry builder + */ + RetryBuilder whenResult(Predicate value); + + /** + * Sets a predicate to determine when an exception should be considered failure + * and retry should be attempted. This is a more general variant of {@link #retryOn(Collection) retryOn}. + * Note that there is no generalized {@link #abortOn(Collection) abortOn}, because all exceptions + * that do not match this predicate are implicitly considered success. + *

+ * If this method is called, {@code retryOn} and {@code abortOn} may not be called. + * + * @param value the predicate, must not be {@code null} + * @return this retry builder + * @see BeforeRetry @BeforeRetry + */ + RetryBuilder whenException(Predicate value); + + /** + * Sets a before retry handler, which is called before each retry, but not before the original attempt. + * + * @param value the before retry handler, must not be {@code null} + * @return this retry builder + * @see BeforeRetry @BeforeRetry + */ + RetryBuilder beforeRetry(Runnable value); + + /** + * Sets a before retry handler, which is called before each retry, but not before the original attempt. + * + * @param value the before retry handler, must not be {@code null} + * @return this retry builder + */ + RetryBuilder beforeRetry(Consumer value); + + /** + * Configures retry to use an exponential backoff instead of the default constant backoff. + *

+ * Only one backoff strategy may be configured, so calling {@link #withFibonacciBackoff()} + * or {@link #withCustomBackoff()} in addition to this method leads to an exception + * during {@link #done()}. + * + * @return the exponential backoff builder + * @see ExponentialBackoff @ExponentialBackoff + */ + ExponentialBackoffBuilder withExponentialBackoff(); + + /** + * Configures retry to use a Fibonacci backoff instead of the default constant backoff. + *

+ * Only one backoff strategy may be configured, so calling {@link #withExponentialBackoff()} + * or {@link #withCustomBackoff()} in addition to this method leads to an exception + * during {@link #done()}. + * + * @return the Fibonacci backoff builder + * @see FibonacciBackoff @FibonacciBackoff + */ + FibonacciBackoffBuilder withFibonacciBackoff(); + + /** + * Configures retry to use a custom backoff instead of the default constant backoff. + *

+ * Only one backoff strategy may be configured, so calling {@link #withExponentialBackoff()} + * or {@link #withFibonacciBackoff()} in addition to this method leads to an exception + * during {@link #done()}. + * + * @return the custom backoff builder + * @see CustomBackoff @CustomBackoff + */ + CustomBackoffBuilder withCustomBackoff(); + + /** + * Sets a callback that will be invoked when a retry is attempted. + *

+ * The callback must be fast and non-blocking and must not throw an exception. + * + * @param callback the retried callback, must not be {@code null} + * @return this retry builder + */ + RetryBuilder onRetry(Runnable callback); + + /** + * Sets a callback that will be invoked when this retry strategy treats a finished invocation as success, + * regardless of whether a retry was attempted or not. + *

+ * The callback must be fast and non-blocking and must not throw an exception. + * + * @param callback the retried callback, must not be {@code null} + * @return this retry builder + */ + RetryBuilder onSuccess(Runnable callback); + + /** + * Sets a callback that will be invoked when this retry strategy treats a finished invocation as failure, + * and no more retries will be attempted. The failure may be caused by depleting the maximum + * number of retries or the maximum duration, or by an exception that is not retryable. + *

+ * The callback must be fast and non-blocking and must not throw an exception. + * + * @param callback the retried callback, must not be {@code null} + * @return this retry builder + */ + RetryBuilder onFailure(Runnable callback); + + /** + * Returns the original fault tolerance builder. + * + * @return the original fault tolerance builder + */ + Builder done(); + + default RetryBuilder with(Consumer consumer) { + consumer.accept(this); + return this; + } + + /** + * Configures an exponential backoff for retry. + * + * @see ExponentialBackoff @ExponentialBackoff + */ + interface ExponentialBackoffBuilder { + /** + * Sets the multiplicative factor used to determine delay between retries. Defaults to 2. + * + * @param value the multiplicative factor, must be >= 1 + * @return this exponential backoff builder + * @see ExponentialBackoff#factor() @ExponentialBackoff.factor + */ + ExponentialBackoffBuilder factor(int value); + + /** + * Sets the maximum delay between retries. Defaults to 1 minute. + * + * @param value the maximum delay, must be >= 0 + * @param unit the maximum delay unit, must not be {@code null} + * @return this exponential backoff builder + * @see ExponentialBackoff#maxDelay() @ExponentialBackoff.maxDelay + * @see ExponentialBackoff#maxDelayUnit() @ExponentialBackoff.maxDelayUnit + */ + ExponentialBackoffBuilder maxDelay(long value, ChronoUnit unit); + + /** + * Returns the original retry builder. + * + * @return the original retry builder + */ + RetryBuilder done(); + + default ExponentialBackoffBuilder with(Consumer consumer) { + consumer.accept(this); + return this; + } + } + + /** + * Configures a Fibonacci backoff for retry. + * + * @see FibonacciBackoff @FibonacciBackoff + */ + interface FibonacciBackoffBuilder { + /** + * Sets the maximum delay between retries. Defaults to 1 minute. + * + * @param value the maximum delay, must be >= 0 + * @param unit the maximum delay unit, must not be {@code null} + * @return this fibonacci backoff builder + * @see FibonacciBackoff#maxDelay() @FibonacciBackoff.maxDelay + * @see FibonacciBackoff#maxDelayUnit() @FibonacciBackoff.maxDelayUnit + */ + FibonacciBackoffBuilder maxDelay(long value, ChronoUnit unit); + + /** + * Returns the original retry builder. + * + * @return the original retry builder + */ + RetryBuilder done(); + + default FibonacciBackoffBuilder with(Consumer consumer) { + consumer.accept(this); + return this; + } + } + + /** + * Configures a custom backoff for retry. + * + * @see CustomBackoff @CustomBackoff + */ + interface CustomBackoffBuilder { + /** + * Sets the custom backoff strategy in the form of a {@link Supplier} of {@link CustomBackoffStrategy} + * instances. Mandatory. + * + * @see CustomBackoff#value() + */ + CustomBackoffBuilder strategy(Supplier value); + + /** + * Returns the original retry builder. + */ + RetryBuilder done(); + + default CustomBackoffBuilder with(Consumer consumer) { + consumer.accept(this); + return this; + } + } + } + + /** + * Configures a timeout. + * + * @see Timeout @Timeout + */ + interface TimeoutBuilder { + /** + * Sets the timeout duration. Defaults to 1 second. + * + * @param value the timeout length, must be >= 0 + * @param unit the timeout unit, must not be {@code null} + * @return this timeout builder + * @see Timeout#value() @Timeout.value + * @see Timeout#unit() @Timeout.unit + */ + TimeoutBuilder duration(long value, ChronoUnit unit); + + /** + * Sets a callback that will be invoked when an invocation times out. + *

+ * The callback must be fast and non-blocking and must not throw an exception. + * + * @param callback the timeout callback, must not be {@code null} + * @return this timeout builder + */ + TimeoutBuilder onTimeout(Runnable callback); + + /** + * Sets a callback that will be invoked when an invocation finishes before the timeout. + *

+ * The callback must be fast and non-blocking and must not throw an exception. + * + * @param callback the finished callback, must not be {@code null} + * @return this timeout builder + */ + TimeoutBuilder onFinished(Runnable callback); + + /** + * Returns the original fault tolerance builder. + * + * @return the original fault tolerance builder + */ + Builder done(); + + default TimeoutBuilder with(Consumer consumer) { + consumer.accept(this); + return this; + } + } + } +} diff --git a/api/src/main/java/io/smallrye/faulttolerance/api/FaultToleranceSpi.java b/api/src/main/java/io/smallrye/faulttolerance/api/Spi.java similarity index 74% rename from api/src/main/java/io/smallrye/faulttolerance/api/FaultToleranceSpi.java rename to api/src/main/java/io/smallrye/faulttolerance/api/Spi.java index 5b4f3b4a..c7fb36a1 100644 --- a/api/src/main/java/io/smallrye/faulttolerance/api/FaultToleranceSpi.java +++ b/api/src/main/java/io/smallrye/faulttolerance/api/Spi.java @@ -1,5 +1,6 @@ package io.smallrye.faulttolerance.api; +import java.lang.reflect.Type; import java.util.function.Function; import io.smallrye.common.annotation.Experimental; @@ -9,13 +10,19 @@ * It should not be used or implemented outside SmallRye Fault Tolerance. */ @Experimental("first attempt at providing programmatic API") -public interface FaultToleranceSpi { +public interface Spi { boolean applies(); int priority(); + Guard.Builder newGuardBuilder(); + + TypedGuard.Builder newTypedGuardBuilder(Type valueType); + + @Deprecated(forRemoval = true) FaultTolerance.Builder newBuilder(Function, R> finisher); + @Deprecated(forRemoval = true) FaultTolerance.Builder newAsyncBuilder(Class asyncType, Function, R> finisher); CircuitBreakerMaintenance circuitBreakerMaintenance(); diff --git a/api/src/main/java/io/smallrye/faulttolerance/api/FaultToleranceSpiAccess.java b/api/src/main/java/io/smallrye/faulttolerance/api/SpiAccess.java similarity index 62% rename from api/src/main/java/io/smallrye/faulttolerance/api/FaultToleranceSpiAccess.java rename to api/src/main/java/io/smallrye/faulttolerance/api/SpiAccess.java index 30835209..84d36362 100644 --- a/api/src/main/java/io/smallrye/faulttolerance/api/FaultToleranceSpiAccess.java +++ b/api/src/main/java/io/smallrye/faulttolerance/api/SpiAccess.java @@ -9,19 +9,19 @@ * It should not be used outside SmallRye Fault Tolerance. */ @Experimental("first attempt at providing programmatic API") -public final class FaultToleranceSpiAccess { +public final class SpiAccess { private static class Holder { - private static final FaultToleranceSpi INSTANCE = instantiateSpi(); + private static final Spi INSTANCE = instantiateSpi(); } - public static FaultToleranceSpi get() { + public static Spi get() { return Holder.INSTANCE; } - private static FaultToleranceSpi instantiateSpi() { - FaultToleranceSpi bestCandidate = null; + private static Spi instantiateSpi() { + Spi bestCandidate = null; int bestCandidatePriority = Integer.MIN_VALUE; - for (FaultToleranceSpi candidate : ServiceLoader.load(FaultToleranceSpi.class)) { + for (Spi candidate : ServiceLoader.load(Spi.class)) { if (!candidate.applies()) { continue; } @@ -32,8 +32,8 @@ private static FaultToleranceSpi instantiateSpi() { } if (bestCandidate == null) { - throw new IllegalStateException("Could not find implementation of FaultToleranceSpi, add a dependency on" - + " io.smallrye:smallrye-fault-tolerance or io.smallrye:smallrye-fault-tolerance-standalone"); + throw new IllegalStateException("Could not find implementation of io.smallrye.faulttolerance.api.Spi, add" + + " a dependency on io.smallrye:smallrye-fault-tolerance or io.smallrye:smallrye-fault-tolerance-standalone"); } return bestCandidate; } diff --git a/api/src/main/java/io/smallrye/faulttolerance/api/TypedGuard.java b/api/src/main/java/io/smallrye/faulttolerance/api/TypedGuard.java new file mode 100644 index 00000000..1f93060b --- /dev/null +++ b/api/src/main/java/io/smallrye/faulttolerance/api/TypedGuard.java @@ -0,0 +1,1012 @@ +package io.smallrye.faulttolerance.api; + +import java.time.temporal.ChronoUnit; +import java.util.Collection; +import java.util.Collections; +import java.util.Objects; +import java.util.concurrent.Callable; +import java.util.concurrent.Executor; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.function.Supplier; + +import jakarta.enterprise.util.TypeLiteral; + +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 io.smallrye.common.annotation.Experimental; + +/** + * Allows guarding an action with various fault tolerance strategies: bulkhead, circuit breaker, fallback, rate limit, + * retry. and timeout. Synchronous as well as asynchronous actions may be guarded, asynchronous actions may optionally + * be offloaded to another thread. + *

+ * An instance of this interface represents a configured set of fault tolerance strategies. It can be used to + * guard a {@link #call(Callable) Callable} or {@link #get(Supplier) Supplier} invocation, or adapt an unguarded + * {@link #adaptCallable(Callable) Callable} or {@link #adaptSupplier(Supplier) Supplier} to a guarded one. + *

+ * The {@link #create(Class)} methods return a builder that allows configuring the supported fault tolerance strategies. + * Order of builder method invocations does not matter, the fault tolerance strategies are always applied + * in a predefined order: fallback > retry > circuit breaker > rate limit > timeout > bulkhead > + * thread offload > guarded action. + *

+ * Note that bulkheads, circuit breakers and rate limits are stateful, so there's a big difference between guarding + * multiple actions using the same {@code TypedGuard} object and using a separate {@code TypedGuard} object for each + * action. Using a single {@code TypedGuard} instance to guard multiple actions means that a single bulkhead, + * circuit breaker and/or rate limit will be shared among all those actions. + *

+ * This API is essentially a programmatic equivalent to the declarative, annotation-based API of MicroProfile Fault + * Tolerance and SmallRye Fault Tolerance. It shares the set of fault tolerance strategies, their invocation order + * and behavior, their configuration properties, etc. Notable differences are: + *

    + *
  • asynchronous actions of type {@link java.util.concurrent.Future} are not supported;
  • + *
  • the fallback, circuit breaker and retry strategies always inspect the cause chain of exceptions, + * following the behavior of SmallRye Fault Tolerance in the non-compatible mode.
  • + *
+ * + * @param type of value of the guarded action + */ +@Experimental("second attempt at providing programmatic API") +public interface TypedGuard { + /** + * Creates a builder for producing a {@link TypedGuard} object representing a set of configured + * fault tolerance strategies that guard given {@code type}. It can be used to execute actions using + * {@link #call(Callable)} or {@link #get(Supplier)}. + *

+ * The given {@code type} is also used to determine if the guarded actions are synchronous or asynchronous. + * Casting to another type is not possible. + */ + static Builder create(Class type) { + return SpiAccess.get().newTypedGuardBuilder(type); + } + + /** + * Creates a builder for producing a {@link TypedGuard} object representing a set of configured + * fault tolerance strategies that guard given {@code type}. It can be used to execute actions using + * {@link #call(Callable) call()} or {@link #get(Supplier) get()}. + *

+ * The given {@code type} is also used to determine if the guarded actions are synchronous or asynchronous. + */ + static Builder create(TypeLiteral type) { + return SpiAccess.get().newTypedGuardBuilder(type.getType()); + } + + /** + * Calls given {@code action} and guards the call by this configured set of fault tolerance strategies. + *

+ * If this {@code TypedGuard} instance was created using a synchronous type, the action is synchronous + * and is always executed on the same thread that calls this method. If this {@code TypedGuard} instance + * was created using an asynchronous type, the action is asynchronous and may be offloaded to another thread + * depending on how the builder was configured. + */ + T call(Callable action) throws Exception; + + /** + * Calls given {@code action} and guards the call by this configured set of fault tolerance strategies. + *

+ * If this {@code TypedGuard} instance was created using a synchronous type, the action is synchronous + * and is always executed on the same thread that calls this method. If this {@code TypedGuard} instance + * was created using an asynchronous type, the action is asynchronous and may be offloaded to another thread + * depending on how the builder was configured. + */ + T get(Supplier action); + + /** + * Adapts given {@code action} to an action guarded by this configured set of fault tolerance strategies. + * Useful when the action has to be called multiple times. + *

+ * Equivalent to {@code () -> call(action)}. + * + * @see #call(Callable) + */ + default Callable adaptCallable(Callable action) { + return () -> call(action); + } + + /** + * Adapts given {@code action} to an action guarded by this configured set of fault tolerance strategies. + * Useful when the action has to be called multiple times. + *

+ * Equivalent to {@code () -> get(action)}. + * + * @see #get(Supplier) + */ + default Supplier adaptSupplier(Supplier action) { + return () -> get(action); + } + + /** + * A builder for configuring fault tolerance strategies. A fault tolerance strategy is included in the resulting + * set if the corresponding {@code with[Strategy]} method is called. Each strategy has its own builder to configure + * the necessary attributes, and each such builder has a {@code done()} method that returns back to this builder. + *

+ * In general, all builders in this API accept multiple invocations of the same method, but only the last + * invocation is meaningful. Any previous invocations are forgotten. + * + * @param type of value of the guarded action + */ + interface Builder { + /** + * Assigns a description to the resulting set of configured fault tolerance strategies. The description + * is used in logging messages and exception messages, and also as an identifier for metrics. + *

+ * The description may be an arbitrary string. Duplicates are permitted. + *

+ * If no description is set, a random UUID is used. + * + * @param value a description, must not be {@code null} + * @return this fault tolerance builder + */ + Builder withDescription(String value); + + /** + * Adds a bulkhead strategy. In this API, bulkhead is a simple concurrency limiter. + * + * @return a builder to configure the bulkhead strategy + * @see Bulkhead @Bulkhead + */ + BulkheadBuilder withBulkhead(); + + /** + * Adds a circuit breaker strategy. + * + * @return a builder to configure the circuit breaker strategy + * @see CircuitBreaker @CircuitBreaker + */ + CircuitBreakerBuilder withCircuitBreaker(); + + /** + * Adds a fallback strategy. + * + * @return a builder to configure the fallback strategy + * @see Fallback @Fallback + */ + FallbackBuilder withFallback(); + + /** + * Adds a rate limit strategy. + * + * @return a builder to configure the rate limit strategy + * @see RateLimit @RateLimit + */ + RateLimitBuilder withRateLimit(); + + /** + * Adds a retry strategy. Retry uses constant backoff between attempts by default, + * but may be configured to use exponential backoff, Fibonacci backoff, or custom backoff. + * + * @return a builder to configure the retry strategy + * @see Retry @Retry + */ + RetryBuilder withRetry(); + + /** + * Adds a timeout strategy. + * + * @return a builder to configure the timeout strategy + * @see Timeout @Timeout + */ + TimeoutBuilder withTimeout(); + + /** + * Configures whether an asynchronous guarded action should be offloaded to another thread. + * + * @param value whether an asynchronous guarded action should be offloaded to another thread + * @return this fault tolerance builder + * @see Asynchronous @Asynchronous + * @see AsynchronousNonBlocking @AsynchronousNonBlocking + */ + Builder withThreadOffload(boolean value); + + /** + * Configures the executor to use when offloading the guarded action to another thread. + *

+ * If this method is not called but thread offload is enabled using {@link #withThreadOffload(boolean)}, + * an asynchronous guarded action is offloaded to the default executor provided by the integrator. + * + * @param executor the executor to which the guarded action should be offloaded + * @return this fault tolerance builder + */ + Builder withThreadOffloadExecutor(Executor executor); + + /** + * Returns a ready-to-use instance of {@code TypedGuard}. + */ + TypedGuard build(); + + /** + * Syntactic sugar for calling the builder methods conditionally without breaking the invocation chain. + * For example: + * + * + *

{@code
+         * TypedGuard.create(...)
+         *     .withRetry() ... .done()
+         *     .with(builder -> {
+         *         if (useTimeout) {
+         *             builder.withTimeout() ... .done();
+         *         }
+         *     })
+         *     .build();
+         * }
+ * + * + * @param consumer block of code to execute with this builder + * @return this fault tolerance builder + */ + default Builder with(Consumer> consumer) { + consumer.accept(this); + return this; + } + + /** + * Configures a bulkhead. + * + * @see Bulkhead @Bulkhead + */ + interface BulkheadBuilder { + /** + * Sets the concurrency limit the bulkhead will enforce. Defaults to 10. + * + * @param value the concurrency limit, must be >= 1 + * @return this bulkhead builder + * @see Bulkhead#value() @Bulkhead.value + */ + BulkheadBuilder limit(int value); + + /** + * Sets the maximum size of the bulkhead queue. Defaults to 10. + * + * @param value the queue size, must be >= 1 + * @return this bulkhead builder + * @see Bulkhead#waitingTaskQueue() @Bulkhead.waitingTaskQueue + */ + BulkheadBuilder queueSize(int value); + + /** + * Enables bulkhead queueing for synchronous actions. + *

+ * If you use this method, you have to ensure that the guard + * is executed on a virtual thread. + * + * @return this bulkhead builder + */ + BulkheadBuilder enableSynchronousQueueing(); + + /** + * Sets a callback that will be invoked when this bulkhead accepts an invocation. + * In case of asynchronous actions, accepting into bulkhead doesn't mean the action + * is immediately invoked; the invocation is first put into a queue and may wait there. + *

+ * The callback must be fast and non-blocking and must not throw an exception. + * + * @param callback the accepted callback, must not be {@code null} + * @return this bulkhead builder + */ + BulkheadBuilder onAccepted(Runnable callback); + + /** + * Sets a callback that will be invoked when this bulkhead rejects an invocation. + *

+ * The callback must be fast and non-blocking and must not throw an exception. + * + * @param callback the rejected callback, must not be {@code null} + * @return this bulkhead builder + */ + BulkheadBuilder onRejected(Runnable callback); + + /** + * Sets a callback that will be invoked when a finished invocation leaves this bulkhead. + *

+ * The callback must be fast and non-blocking and must not throw an exception. + * + * @param callback the finished callback, must not be {@code null} + * @return this bulkhead builder + */ + BulkheadBuilder onFinished(Runnable callback); + + /** + * Returns the original fault tolerance builder. + * + * @return the original fault tolerance builder + */ + Builder done(); + + default BulkheadBuilder with(Consumer> consumer) { + consumer.accept(this); + return this; + } + } + + /** + * Configures a circuit breaker. + * + * @see CircuitBreaker @CircuitBreaker + */ + interface CircuitBreakerBuilder { + /** + * Sets the set of exception types considered failure. Defaults to all exceptions ({@code Throwable}). + * + * @param value collection of exception types, must not be {@code null} + * @return this circuit breaker builder + * @see CircuitBreaker#failOn() @CircuitBreaker.failOn + */ + CircuitBreakerBuilder failOn(Collection> value); + + /** + * Equivalent to {@link #failOn(Collection) failOn(Collections.singleton(value))}. + * + * @param value an exception class, must not be {@code null} + * @return this circuit breaker builder + */ + default CircuitBreakerBuilder failOn(Class value) { + return failOn(Collections.singleton(Objects.requireNonNull(value))); + } + + /** + * Sets the set of exception types considered success. Defaults to no exception (empty set). + * + * @param value collection of exception types, must not be {@code null} + * @return this circuit breaker builder + * @see CircuitBreaker#skipOn() @CircuitBreaker.skipOn + */ + CircuitBreakerBuilder skipOn(Collection> value); + + /** + * Equivalent to {@link #skipOn(Collection) skipOn(Collections.singleton(value))}. + * + * @param value an exception class, must not be {@code null} + * @return this circuit breaker builder + */ + default CircuitBreakerBuilder skipOn(Class value) { + return skipOn(Collections.singleton(Objects.requireNonNull(value))); + } + + /** + * Sets a predicate to determine when an exception should be considered failure + * by the circuit breaker. This is a more general variant of {@link #failOn(Collection) failOn}. + * Note that there is no generalized {@link #skipOn(Collection) skipOn}, because all exceptions + * that do not match this predicate are implicitly considered success. + *

+ * If this method is called, {@code failOn} and {@code skipOn} may not be called. + * + * @param value the predicate, must not be {@code null} + * @return this circuit breaker builder + */ + CircuitBreakerBuilder when(Predicate value); + + /** + * Sets the delay after which an open circuit moves to half-open. Defaults to 5 seconds. + * + * @param value the delay length, must be >= 0 + * @param unit the delay unit, must not be {@code null} + * @return this circuit breaker builder + * @see CircuitBreaker#delay() @CircuitBreaker.delay + * @see CircuitBreaker#delayUnit() @CircuitBreaker.delayUnit + */ + CircuitBreakerBuilder delay(long value, ChronoUnit unit); + + /** + * Sets the size of the circuit breaker's rolling window. + * + * @param value the size of the circuit breaker's rolling window, must be >= 1 + * @return this circuit breaker builder + * @see CircuitBreaker#requestVolumeThreshold() @CircuitBreaker.requestVolumeThreshold + */ + CircuitBreakerBuilder requestVolumeThreshold(int value); + + /** + * Sets the failure ratio that, once reached, will move a closed circuit breaker to open. Defaults to 0.5. + * + * @param value the failure ratio, must be >= 0 and <= 1 + * @return this circuit breaker builder + * @see CircuitBreaker#failureRatio() @CircuitBreaker.failureRatio + */ + CircuitBreakerBuilder failureRatio(double value); + + /** + * Sets the number of successful executions that, once reached, will move a half-open circuit breaker + * to closed. Defaults to 1. + * + * @param value the number of successful executions, must be >= 1 + * @return this circuit breaker builder + * @see CircuitBreaker#successThreshold() @CircuitBreaker.successThreshold + */ + CircuitBreakerBuilder successThreshold(int value); + + /** + * Sets a circuit breaker name. Required to use the {@link CircuitBreakerMaintenance} methods. + * Defaults to unnamed. It is an error to use the same name for multiple circuit breakers. + *

+ * If a circuit breaker is not given a name, its state will not be affected + * by {@link CircuitBreakerMaintenance#resetAll()}. This is unlike unnamed circuit breakers + * declared using {@code @CircuitBreaker}, because there's a fixed number of circuit breakers + * created using the declarative API, but a potentially unbounded number of circuit breakers + * created using the programmatic API. In other words, automatically remembering all + * circuit breakers created using the programmatic API would easily lead to a memory leak. + * + * @param value the circuit breaker name, must not be {@code null} + * @return this circuit breaker builder + * @see CircuitBreakerName @CircuitBreakerName + */ + CircuitBreakerBuilder name(String value); + + /** + * Sets a callback that will be invoked upon each state change of this circuit breaker. + *

+ * The callback must be fast and non-blocking and must not throw an exception. + * + * @param callback the state change callback, must not be {@code null} + * @return this circuit breaker builder + */ + CircuitBreakerBuilder onStateChange(Consumer callback); + + /** + * Sets a callback that will be invoked when this circuit breaker treats a finished invocation as success. + *

+ * The callback must be fast and non-blocking and must not throw an exception. + * + * @param callback the success callback, must not be {@code null} + * @return this circuit breaker builder + */ + CircuitBreakerBuilder onSuccess(Runnable callback); + + /** + * Sets a callback that will be invoked when this circuit breaker treats a finished invocation as failure. + *

+ * The callback must be fast and non-blocking and must not throw an exception. + * + * @param callback the failure callback, must not be {@code null} + * @return this circuit breaker builder + */ + CircuitBreakerBuilder onFailure(Runnable callback); + + /** + * Sets a callback that will be invoked when this circuit breaker prevents an invocation, because it is + * in the open or half-open state. + *

+ * The callback must be fast and non-blocking and must not throw an exception. + * + * @param callback the prevented callback, must not be {@code null} + * @return this circuit breaker builder + */ + CircuitBreakerBuilder onPrevented(Runnable callback); + + /** + * Returns the original fault tolerance builder. + * + * @return the original fault tolerance builder + */ + Builder done(); + + default CircuitBreakerBuilder with(Consumer> consumer) { + consumer.accept(this); + return this; + } + } + + /** + * Configures a fallback. + * + * @see Fallback @Fallback + */ + interface FallbackBuilder { + /** + * Sets the fallback handler in the form of a fallback value {@link Supplier}. + * + * @param value the fallback value supplier, must not be {@code null} + * @return this fallback builder + */ + FallbackBuilder handler(Supplier value); + + /** + * Sets the fallback handler in the form of a {@link Function} that transforms the exception + * to the fallback value. + * + * @param value the fallback value function, must not be {@code null} + * @return this fallback builder + */ + FallbackBuilder handler(Function value); + + /** + * Sets the set of exception types considered failure. Defaults to all exceptions ({@code Throwable}). + * + * @param value collection of exception types, must not be {@code null} + * @return this fallback builder + * @see Fallback#applyOn() @Fallback.applyOn + */ + FallbackBuilder applyOn(Collection> value); + + /** + * Equivalent to {@link #applyOn(Collection) applyOn(Collections.singleton(value))}. + * + * @param value an exception class, must not be {@code null} + * @return this fallback builder + */ + default FallbackBuilder applyOn(Class value) { + return applyOn(Collections.singleton(Objects.requireNonNull(value))); + } + + /** + * Sets the set of exception types considered success. Defaults to no exception (empty set). + * + * @param value collection of exception types, must not be {@code null} + * @return this fallback builder + * @see Fallback#skipOn() @Fallback.skipOn + */ + FallbackBuilder skipOn(Collection> value); + + /** + * Equivalent to {@link #skipOn(Collection) skipOn(Collections.singleton(value))}. + * + * @param value an exception class, must not be {@code null} + * @return this fallback builder + */ + default FallbackBuilder skipOn(Class value) { + return skipOn(Collections.singleton(Objects.requireNonNull(value))); + } + + /** + * Sets a predicate to determine when an exception should be considered failure + * and fallback should be applied. This is a more general variant of {@link #applyOn(Collection) applyOn}. + * Note that there is no generalized {@link #skipOn(Collection) skipOn}, because all exceptions + * that do not match this predicate are implicitly considered success. + *

+ * If this method is called, {@code applyOn} and {@code skipOn} may not be called. + * + * @param value the predicate, must not be {@code null} + * @return this fallback builder + */ + FallbackBuilder when(Predicate value); + + /** + * Returns the original fault tolerance builder. + * + * @return the original fault tolerance builder + */ + Builder done(); + + default FallbackBuilder with(Consumer> consumer) { + consumer.accept(this); + return this; + } + } + + /** + * Configures a rate limit. + * + * @see RateLimit @RateLimit + */ + interface RateLimitBuilder { + /** + * Sets the maximum number of invocations in a time window. Defaults to 100. + * + * @param value maximum number of invocations in a time window, must be >= 1 + * @return this rate limit builder + * @see RateLimit#value() @RateLimit.value + */ + RateLimitBuilder limit(int value); + + /** + * Sets the time window length. Defaults to 1 second. + * + * @param value the time window size, must be >= 1 + * @return this rate limit builder + * @see RateLimit#window() @RateLimit.window + * @see RateLimit#windowUnit() @RateLimit.windowUnit + */ + RateLimitBuilder window(long value, ChronoUnit unit); + + /** + * Sets the minimum spacing between invocations. Defaults to 0. + * + * @param value the minimum spacing, must be >= 0 + * @return this rate limit builder + * @see RateLimit#minSpacing() @RateLimit.minSpacing + * @see RateLimit#minSpacingUnit() @RateLimit.minSpacingUnit + */ + RateLimitBuilder minSpacing(long value, ChronoUnit unit); + + /** + * Sets the type of time windows used for rate limiting. Defaults to {@link RateLimitType#FIXED}. + * + * @param value the time window type, must not be {@code null} + * @return this rate limit builder + * @see RateLimit#type() @RateLimit.type + */ + RateLimitBuilder type(RateLimitType value); + + /** + * Sets a callback that will be invoked when this rate limit permits an invocation. + *

+ * The callback must be fast and non-blocking and must not throw an exception. + * + * @param callback the permitted callback, must not be {@code null} + * @return this rate limit builder + */ + RateLimitBuilder onPermitted(Runnable callback); + + /** + * Sets a callback that will be invoked when this rate limit rejects an invocation. + *

+ * The callback must be fast and non-blocking and must not throw an exception. + * + * @param callback the rejected callback, must not be {@code null} + * @return this rate limit builder + */ + RateLimitBuilder onRejected(Runnable callback); + + /** + * Returns the original fault tolerance builder. + * + * @return the original fault tolerance builder + */ + Builder done(); + + default RateLimitBuilder with(Consumer> consumer) { + consumer.accept(this); + return this; + } + } + + /** + * Configures a retry. + * + * @see Retry @Retry + */ + interface RetryBuilder { + /** + * Sets the maximum number of retries. Defaults to 3. + * + * @param value the maximum number of retries, must be >= -1. + * @return this retry builder + * @see Retry#maxRetries() @Retry.maxRetries + */ + RetryBuilder maxRetries(int value); + + /** + * Sets the delay between retries. Defaults to 0. + * + * @param value the delay length, must be >= 0 + * @param unit the delay unit, must not be {@code null} + * @return this retry builder + * @see Retry#delay() @Retry.delay + * @see Retry#delayUnit() @Retry.delayUnit + */ + RetryBuilder delay(long value, ChronoUnit unit); + + /** + * Sets the maximum duration of all invocations, including possible retries. Defaults to 3 minutes. + * + * @param value the maximum duration length, must be >= 0 + * @param unit the maximum duration unit, must not be {@code null} + * @return this retry builder + * @see Retry#maxDuration() @Retry.maxDuration + * @see Retry#durationUnit() @Retry.durationUnit + */ + RetryBuilder maxDuration(long value, ChronoUnit unit); + + /** + * Sets the jitter bound. Random value in the range from {@code -jitter} to {@code +jitter} will be added + * to the delay between retry attempts. Defaults to 200 millis. + * + * @param value the jitter bound length, must be >= 0 + * @param unit the jitter bound unit, must not be {@code null} + * @return this retry builder + * @see Retry#jitter() @Retry.jitter + * @see Retry#jitterDelayUnit() @Retry.jitterDelayUnit + */ + RetryBuilder jitter(long value, ChronoUnit unit); + + /** + * Sets the set of exception types considered failure. Defaults to all exceptions ({@code Exception}). + * + * @param value collection of exception types, must not be {@code null} + * @return this retry builder + * @see Retry#retryOn() @Retry.retryOn + */ + RetryBuilder retryOn(Collection> value); + + /** + * Equivalent to {@link #retryOn(Collection) retryOn(Collections.singleton(value))}. + * + * @param value an exception class, must not be {@code null} + * @return this retry builder + */ + default RetryBuilder retryOn(Class value) { + return retryOn(Collections.singleton(Objects.requireNonNull(value))); + } + + /** + * Sets the set of exception types considered success. Defaults to no exception (empty set). + * + * @param value collection of exception types, must not be {@code null} + * @return this retry builder + * @see Retry#abortOn() @Retry.abortOn + */ + RetryBuilder abortOn(Collection> value); + + /** + * Equivalent to {@link #abortOn(Collection) abortOn(Collections.singleton(value))}. + * + * @param value an exception class, must not be {@code null} + * @return this retry builder + */ + default RetryBuilder abortOn(Class value) { + return abortOn(Collections.singleton(Objects.requireNonNull(value))); + } + + /** + * Sets a predicate to determine when a result should be considered failure and retry + * should be attempted. All results that do not match this predicate are implicitly + * considered success. + * + * @param value the predicate, must not be {@code null} + * @return this retry builder + */ + RetryBuilder whenResult(Predicate value); + + /** + * Sets a predicate to determine when an exception should be considered failure + * and retry should be attempted. This is a more general variant of {@link #retryOn(Collection) retryOn}. + * Note that there is no generalized {@link #abortOn(Collection) abortOn}, because all exceptions + * that do not match this predicate are implicitly considered success. + *

+ * If this method is called, {@code retryOn} and {@code abortOn} may not be called. + * + * @param value the predicate, must not be {@code null} + * @return this retry builder + * @see BeforeRetry @BeforeRetry + */ + RetryBuilder whenException(Predicate value); + + /** + * Sets a before retry handler, which is called before each retry, but not before the original attempt. + * + * @param value the before retry handler, must not be {@code null} + * @return this retry builder + * @see BeforeRetry @BeforeRetry + */ + RetryBuilder beforeRetry(Runnable value); + + /** + * Sets a before retry handler, which is called before each retry, but not before the original attempt. + * + * @param value the before retry handler, must not be {@code null} + * @return this retry builder + */ + RetryBuilder beforeRetry(Consumer value); + + /** + * Configures retry to use an exponential backoff instead of the default constant backoff. + *

+ * Only one backoff strategy may be configured, so calling {@link #withFibonacciBackoff()} + * or {@link #withCustomBackoff()} in addition to this method leads to an exception + * during {@link #done()}. + * + * @return the exponential backoff builder + * @see ExponentialBackoff @ExponentialBackoff + */ + ExponentialBackoffBuilder withExponentialBackoff(); + + /** + * Configures retry to use a Fibonacci backoff instead of the default constant backoff. + *

+ * Only one backoff strategy may be configured, so calling {@link #withExponentialBackoff()} + * or {@link #withCustomBackoff()} in addition to this method leads to an exception + * during {@link #done()}. + * + * @return the Fibonacci backoff builder + * @see FibonacciBackoff @FibonacciBackoff + */ + FibonacciBackoffBuilder withFibonacciBackoff(); + + /** + * Configures retry to use a custom backoff instead of the default constant backoff. + *

+ * Only one backoff strategy may be configured, so calling {@link #withExponentialBackoff()} + * or {@link #withFibonacciBackoff()} in addition to this method leads to an exception + * during {@link #done()}. + * + * @return the custom backoff builder + * @see CustomBackoff @CustomBackoff + */ + CustomBackoffBuilder withCustomBackoff(); + + /** + * Sets a callback that will be invoked when a retry is attempted. + *

+ * The callback must be fast and non-blocking and must not throw an exception. + * + * @param callback the retried callback, must not be {@code null} + * @return this retry builder + */ + RetryBuilder onRetry(Runnable callback); + + /** + * Sets a callback that will be invoked when this retry strategy treats a finished invocation as success, + * regardless of whether a retry was attempted or not. + *

+ * The callback must be fast and non-blocking and must not throw an exception. + * + * @param callback the retried callback, must not be {@code null} + * @return this retry builder + */ + RetryBuilder onSuccess(Runnable callback); + + /** + * Sets a callback that will be invoked when this retry strategy treats a finished invocation as failure, + * and no more retries will be attempted. The failure may be caused by depleting the maximum + * number of retries or the maximum duration, or by an exception that is not retryable. + *

+ * The callback must be fast and non-blocking and must not throw an exception. + * + * @param callback the retried callback, must not be {@code null} + * @return this retry builder + */ + RetryBuilder onFailure(Runnable callback); + + /** + * Returns the original fault tolerance builder. + * + * @return the original fault tolerance builder + */ + Builder done(); + + default RetryBuilder with(Consumer> consumer) { + consumer.accept(this); + return this; + } + + /** + * Configures an exponential backoff for retry. + * + * @see ExponentialBackoff @ExponentialBackoff + */ + interface ExponentialBackoffBuilder { + /** + * Sets the multiplicative factor used to determine delay between retries. Defaults to 2. + * + * @param value the multiplicative factor, must be >= 1 + * @return this exponential backoff builder + * @see ExponentialBackoff#factor() @ExponentialBackoff.factor + */ + ExponentialBackoffBuilder factor(int value); + + /** + * Sets the maximum delay between retries. Defaults to 1 minute. + * + * @param value the maximum delay, must be >= 0 + * @param unit the maximum delay unit, must not be {@code null} + * @return this exponential backoff builder + * @see ExponentialBackoff#maxDelay() @ExponentialBackoff.maxDelay + * @see ExponentialBackoff#maxDelayUnit() @ExponentialBackoff.maxDelayUnit + */ + ExponentialBackoffBuilder maxDelay(long value, ChronoUnit unit); + + /** + * Returns the original retry builder. + * + * @return the original retry builder + */ + RetryBuilder done(); + + default ExponentialBackoffBuilder with(Consumer> consumer) { + consumer.accept(this); + return this; + } + } + + /** + * Configures a Fibonacci backoff for retry. + * + * @see FibonacciBackoff @FibonacciBackoff + */ + interface FibonacciBackoffBuilder { + /** + * Sets the maximum delay between retries. Defaults to 1 minute. + * + * @param value the maximum delay, must be >= 0 + * @param unit the maximum delay unit, must not be {@code null} + * @return this fibonacci backoff builder + * @see FibonacciBackoff#maxDelay() @FibonacciBackoff.maxDelay + * @see FibonacciBackoff#maxDelayUnit() @FibonacciBackoff.maxDelayUnit + */ + FibonacciBackoffBuilder maxDelay(long value, ChronoUnit unit); + + /** + * Returns the original retry builder. + * + * @return the original retry builder + */ + RetryBuilder done(); + + default FibonacciBackoffBuilder with(Consumer> consumer) { + consumer.accept(this); + return this; + } + } + + /** + * Configures a custom backoff for retry. + * + * @see CustomBackoff @CustomBackoff + */ + interface CustomBackoffBuilder { + /** + * Sets the custom backoff strategy in the form of a {@link Supplier} of {@link CustomBackoffStrategy} + * instances. Mandatory. + * + * @see CustomBackoff#value() + */ + CustomBackoffBuilder strategy(Supplier value); + + /** + * Returns the original retry builder. + */ + RetryBuilder done(); + + default CustomBackoffBuilder with(Consumer> consumer) { + consumer.accept(this); + return this; + } + } + } + + /** + * Configures a timeout. + * + * @see Timeout @Timeout + */ + interface TimeoutBuilder { + /** + * Sets the timeout duration. Defaults to 1 second. + * + * @param value the timeout length, must be >= 0 + * @param unit the timeout unit, must not be {@code null} + * @return this timeout builder + * @see Timeout#value() @Timeout.value + * @see Timeout#unit() @Timeout.unit + */ + TimeoutBuilder duration(long value, ChronoUnit unit); + + /** + * Sets a callback that will be invoked when an invocation times out. + *

+ * The callback must be fast and non-blocking and must not throw an exception. + * + * @param callback the timeout callback, must not be {@code null} + * @return this timeout builder + */ + TimeoutBuilder onTimeout(Runnable callback); + + /** + * Sets a callback that will be invoked when an invocation finishes before the timeout. + *

+ * The callback must be fast and non-blocking and must not throw an exception. + * + * @param callback the finished callback, must not be {@code null} + * @return this timeout builder + */ + TimeoutBuilder onFinished(Runnable callback); + + /** + * Returns the original fault tolerance builder. + * + * @return the original fault tolerance builder + */ + Builder done(); + + default TimeoutBuilder with(Consumer> consumer) { + consumer.accept(this); + return this; + } + } + } +} diff --git a/api/src/test/java/io/smallrye/faulttolerance/api/GuardBuilderConsistencyTest.java b/api/src/test/java/io/smallrye/faulttolerance/api/GuardBuilderConsistencyTest.java new file mode 100644 index 00000000..873bb062 --- /dev/null +++ b/api/src/test/java/io/smallrye/faulttolerance/api/GuardBuilderConsistencyTest.java @@ -0,0 +1,59 @@ +package io.smallrye.faulttolerance.api; + +import java.lang.reflect.Method; +import java.util.Map; + +import org.junit.jupiter.api.Test; + +public class GuardBuilderConsistencyTest { + private final Map, Class> types = Map.of( + Guard.Builder.class, + TypedGuard.Builder.class, + + Guard.Builder.BulkheadBuilder.class, + TypedGuard.Builder.BulkheadBuilder.class, + + Guard.Builder.CircuitBreakerBuilder.class, + TypedGuard.Builder.CircuitBreakerBuilder.class, + + // no `FallbackBuilder`, that only exists on `TypedGuard` + + Guard.Builder.RateLimitBuilder.class, + TypedGuard.Builder.RateLimitBuilder.class, + + Guard.Builder.RetryBuilder.class, + TypedGuard.Builder.RetryBuilder.class, + + Guard.Builder.RetryBuilder.CustomBackoffBuilder.class, + TypedGuard.Builder.RetryBuilder.CustomBackoffBuilder.class, + + Guard.Builder.RetryBuilder.ExponentialBackoffBuilder.class, + TypedGuard.Builder.RetryBuilder.ExponentialBackoffBuilder.class, + + Guard.Builder.RetryBuilder.FibonacciBackoffBuilder.class, + TypedGuard.Builder.RetryBuilder.FibonacciBackoffBuilder.class, + + Guard.Builder.TimeoutBuilder.class, + TypedGuard.Builder.TimeoutBuilder.class); + + @Test + public void test() throws NoSuchMethodException { + for (Map.Entry, Class> entry : types.entrySet()) { + Class guardClass = entry.getKey(); + Class typedGuardClass = entry.getValue(); + + for (Method guardMethod : guardClass.getDeclaredMethods()) { + typedGuardClass.getDeclaredMethod(guardMethod.getName(), guardMethod.getParameterTypes()); + } + + for (Method typedGuardMethod : typedGuardClass.getDeclaredMethods()) { + if (TypedGuard.Builder.class.equals(typedGuardClass) + && "withFallback".equalsIgnoreCase(typedGuardMethod.getName())) { + continue; + } + + guardClass.getDeclaredMethod(typedGuardMethod.getName(), typedGuardMethod.getParameterTypes()); + } + } + } +} diff --git a/doc/modules/ROOT/pages/internals/logging.adoc b/doc/modules/ROOT/pages/internals/logging.adoc index 24570a78..6ee45e16 100644 --- a/doc/modules/ROOT/pages/internals/logging.adoc +++ b/doc/modules/ROOT/pages/internals/logging.adoc @@ -7,7 +7,7 @@ Message codes are divided into ranges that are defined in the `message-ranges.tx In addition to logging, JBoss Logging Tools can also be used for creating exceptions, again, with out of the box support for message codes and internationalization. Contrary to both documentation sites mentioned above, it _is_ possible to mix logging and exceptions in a single interface, as demonstrated by `CdiLogger`. -However, {smallrye-fault-tolerance} doesn't use this feature yet, with the exception of `CdiLogger.multipleCircuitBreakersWithTheSameName`. +However, {smallrye-fault-tolerance} doesn't use this feature yet, with the exception of a few methods in `CdiLogger`. This should be rectified in the future. Note that `DEBUG` and `TRACE` logging doesn't need message codes or internationalization, so we usually don't declare logger methods for them, unless the log message requires parameters. diff --git a/doc/modules/ROOT/pages/reference/programmatic-api.adoc b/doc/modules/ROOT/pages/reference/programmatic-api.adoc index 5a08caf7..ee95fd94 100644 --- a/doc/modules/ROOT/pages/reference/programmatic-api.adoc +++ b/doc/modules/ROOT/pages/reference/programmatic-api.adoc @@ -33,14 +33,24 @@ The API is brought in transitively. == Usage -The entrypoint to the programmatic API is the `FaultTolerance` interface. +The entrypoints to the programmatic API are the `Guard` and `TypedGuard` interfaces. -This interface represents a configured set of fault tolerance strategies. +These interfaces represent a configured set of fault tolerance strategies. Their configuration, order of application and behavior in general corresponds to the declarative API, so if you know that, you'll feel right at home. If not, the javadoc has all the information you need (though it often points to the annotation-based API for more information). -To create an instance of `FaultTolerance`, you can use the `create` and `createAsync` static methods. -They return a builder which has to be used to add and configure all the fault tolerance strategies that should apply. +The interfaces are very similar, there are only 2 differences: + +. `Guard` requires specifying the return type of the guarded operation for each `call()` or `get()` (or `adapt*`) method call. + `TypedGuard`, on the other hand, requires specifying the return type once, when creating an instance using `create()`. + Therefore, `Guard` may be used for guarding many different types, while `TypedGuard` may only be used to guard single type. +. `TypedGuard` allows defining a fallback. + `Guard` does not. + +In the following text, we'll only talk about `Guard`, but it also applies to `TypedGuard` without a change (except of the differences mentioned above). + +To create an instance of `Guard`, you can use the `create` static method. +It returns a builder which has to be used to add and configure all the fault tolerance strategies that should apply. There is no external configuration, so all configuration properties have to be set explicitly, using the builder methods. If you don't set a configuration property, it will default to the same value the annotation-based API uses. @@ -57,7 +67,7 @@ Everything else will be ignored. Note that this is somewhat different to the declarative, annotation-based API, where only fallback is retained and the `@Asynchronous` strategy is skipped as well. Since this significantly changes execution semantics, the programmatic API will apply thread offload even if fault tolerance is disabled. -Similarly to the declarative API, implementations of the programmatic API also read this property only once, when the `FaultTolerance` API is first used. +Similarly to the declarative API, implementations of the programmatic API also read this property only once, when the `Guard` API is first used. It is _not_ read again later. **** @@ -66,28 +76,19 @@ Let's take a look at a simple example: [source,java] ---- public class MyService { - private static final FaultTolerance guarded = FaultTolerance.create() // <1> - .withFallback().handler(() -> "fallback").done() // <2> - .build(); // <3> + private static final Guard GUARD = Guard.create() + .withRetry().maxRetries(3).done() + .build(); public String hello() throws Exception { - return guarded.call(() -> externalService.hello()); // <4> + return GUARD.call(() -> externalService.hello(), String.class); // <1> } } ---- -<1> The `create` invocation typically has to include an explicit type argument (``). - The `FaultTolerance` interface has a type parameter which should be set to the return type of the guarded actions. - This is required to be able to guarantee that a fallback value is of the same type. -<2> The fallback handler may be a simple supplier of the fallback value, or a function that takes the exception and transforms it to the fallback value. - Here, we use the simpler option. - Note that we don't set any other configuration options. - This means that the default set of exceptions is used to determine when fallback should apply. -<3> The `build` method returns a `FaultTolerance` instance, which can later be used to guard arbitrary `String`-returning actions. -<4> Here, we call `externalService.hello()` and guard the call with the previously configured set of fault tolerance strategies. - (In this case, just fallback.) - The `call` method uses the `Callable` type to represent the called action. - Similar methods exist that accept a `Supplier` (`get`) or `Runnable` (`run`). +<1> Here, we call `externalService.hello()` and guard the call with the previously configured set of fault tolerance strategies. + The `call` method used here takes the `Callable` type, which represent the guarded action. + The `get` method works just like `call`, but accepts a `Supplier`. The previous example shows how to apply fault tolerance to synchronous actions. {smallrye-fault-tolerance} naturally also supports guarding asynchronous actions, using the `CompletionStage` type. @@ -96,110 +97,102 @@ Unlike the declarative API, the programmatic API doesn't support asynchronous ac [source,java] ---- public class MyService { - private static final FaultTolerance> guarded = FaultTolerance.createAsync() // <1> - .withBulkhead().done() // <2> - .withThreadOffload(true) // <3> + private static final Guard GUARD = Guard.create() + .withBulkhead().done() + .withThreadOffload(true) // <1> .build(); public CompletionStage hello() throws Exception { - return guarded.call(() -> externalService.hello()); // <4> + return GUARD.call(() -> externalService.hello(), + new TypeLiteral>() {}); // <2> } } ---- -<1> The `createAsync` method takes a type parameter of `String` and returns a builder for fault tolerance of type `CompletionStage`. -<2> Here, we add a bulkhead. - Since we don't configure any property, default values are used. - That is, at most 10 concurrent executions are permitted, and 10 more executions may be waiting in a queue. -<3> And here, we add a thread offload. - This is only possible for asynchronous actions, and corresponds to the `@Asynchronous` annotation from {microprofile-fault-tolerance}. -<4> Note that here, unlike the previous example, the `externalService.hello()` method is assumed to return `CompletionStage`. +<1> The thread offload here only applies to asynchronous actions. + If you use the same `GUARD` to guard a synchronous action, no thread offload will apply. +<2> Note that here, we use the `TypeLiteral` class (from CDI) to specify the type of the guarded action. Asynchronous actions may be blocking or non-blocking. In the example above, we assume the `externalService.hello()` call is blocking, so we set thread offload to `true`. {smallrye-fault-tolerance} will automatically move the actual execution of the action to another thread. -If we didn't configure `withThreadOffload`, however, the execution would continue on the original thread. +If we didn't configure `withThreadOffload`, however, the execution of an asynchronous action would continue on the original thread. This is often desired for non-blocking actions, which are very common in modern reactive architectures. Also note that in this example, we configured multiple fault tolerance strategies: bulkhead and thread offload. -When that happens, the fault tolerance strategies are ordered according to the MicroProfile Fault Tolerance specification, just like in the declarative API. +When that happens, the fault tolerance strategies are ordered according to the {microprofile-fault-tolerance} specification, just like in the declarative API. Order of all the `with*` method invocations doesn’t matter. -=== Synchronous vs. Asynchronous - -What's the difference between `FaultTolerance.>create()` and `FaultTolerance.createAsync()`? -Both may be used to guard an action that returns `CompletionStage`, correct? - -Well, yes and no. - -The synchronous variant (created using `create()`) will only guard the synchronous part of the action -- the part that ends by returning the `CompletionStage` instance. -It will _not_ guard the asynchronous behavior. - -For example, if an action returns a `CompletionStage` object, synchronous fault tolerance will consider that action successfully finished. -If that `CompletionStage` later completes with an exception, synchronous fault tolerance will never know. -What's more, the fact that this action has already "finished" means that the action will also leave the bulkhead, so concurrency limiting will not work properly. - -The asynchronous variant (created using `createAsync()`), on the other hand, will not treat the action as finished until the `CompletionStage` actually completes. -That is, the asynchronous action will only leave the bulkhead when it's complete, so concurrency limiting works as expected. -And if the `CompletionStage` completes exceptionally, asynchronous fault tolerance will treat that as a failure and react accordingly. - -To summarize, if you need to guard asynchronous actions, blocking or non-blocking, always use `createAsync`. - === Single-Action Usage -The `FaultTolerance` API is general and permits guarding multiple different actions using the same set of fault tolerance strategies. -Often, that isn't necessary and we need to guard just a single action, altough possibly several times. - -For such use case, the `FaultTolerance` API provides shortcuts that work with the `Callable`, `Supplier` and `Runnable` types. +The `Guard` API is general and permits guarding multiple different actions using the same set of fault tolerance strategies. +Often, we need to guard just a single action, although possibly several times. -First off, a `FaultTolerance` instance may be adapted to a `Callable`, `Supplier` or `Runnable` using the `adapt*` methods. -For example: +For such use case, the `Guard` instance may be adapted to a `Callable` or `Supplier` using the `adapt*` methods: [source,java] ---- public class MyService { - private static final FaultTolerance guard = FaultTolerance.create() + private static final Callable guard = Guard.create() .withTimeout().duration(5, ChronoUnit.SECONDS).done() - .build(); // <1> + .build() // <1> + .adaptCallable(() -> externalService.hello(), String.class); // <2> public String hello() throws Exception { - Callable callable = guard.adaptCallable(() -> externalService.hello()); // <2> - return callable.call(); // <3> } } ---- -<1> Create a `FaultTolerance` object that can guard arbitrary `String`-returning actions. -<2> Adapt the general `FaultTolerance` instance to a `Callable` that guards the `externalService.hello()` invocation. - Similar methods exist that accept and return a `Supplier` (`adaptSupplier`) and `Runnable` (`adaptRunnable`). +<1> Create a `Guard` object that can guard arbitrary actions. +<2> Adapt the general `Guard` instance to a `Callable` that guards the `externalService.hello()` invocation. + Similar method exists that accepts and returns a `Supplier`: `adaptSupplier`. <3> You can do whatever you wish with the adapted `Callable`. Here, we just call it once, which isn't very interesting, but it could possibly be called multiple times, passed to other methods etc. -This style of usage still creates a `FaultTolerance` instance first. -If that is not necessary, you can create a `Callable`, `Supplier` or `Runnable` directly: +=== Synchronous vs. Asynchronous + +The `Guard` and `TypedGuard` interfaces both decide whether the guarded action is synchronous or asynchronous based on the action type. +This is given to `Guard` when calling or adapting the action, and to `TypedGuard` when creating it. + +If the type is asynchronous (such as `CompletionStage`), the asynchronous behavior will also be guarded; the action is only considered complete when the `CompletionStage` actually completes. +If the type is not asynchronous, only the synchronous behavior will be guarded; the action is considered complete when the method returns. + +==== Mutiny Support + +It is enough to include the Mutiny support library `io.smallrye:smallrye-fault-tolerance-mutiny`, as described in xref:reference/asynchronous.adoc#async-types[Additional Asynchronous Types]. +With this library present, both `Guard` and `TypedGuard` will recognize `Uni` as an asynchronous type and guard it properly. +Guarding a `Multi` is not supported. + +For example: [source,java] ---- public class MyService { - private static final Callable guard = FaultTolerance.createCallable(() -> externalService.hello()) // <1> + private final Supplier> GUARD = TypedGuard.create( + new TypeLiteral>() {}) .withTimeout().duration(5, ChronoUnit.SECONDS).done() - .build(); + .withFallback().handler(() -> Uni.createFrom().item("fallback")).done() + .build() + .adaptSupplier(() -> externalService.hello()); // <1> - public String hello() throws Exception { - return guard.call(); // <2> + public Uni hello() { + return guard.get(); } } ---- -<1> The `createCallable` method returns a fault tolerance builder that provides the same configuration options, but in the end, returns a `Callable`. - In this case, a `Callable`. - These methods typically don't require an explicit type argument, because it can be inferred from the type of action passed in. - Similar methods exist that return a builder which, in the end, returns a `Supplier` (`createSupplier`) or `Runnable` (`createRunnable`). -<2> Here, we don't have to do anything special, just call the existing `Callable`. - Again, it could possibly be called multiple times, passed to other methods etc. +<1> The call to `externalService.hello()` is supposed to return `Uni`. + +Note that the `Uni` type is lazy, so the action itself won't execute until the guarded `Uni` is subscribed to. + +.Quarkus +**** +In Quarkus, the Mutiny support library is present by default. +You can guard `Uni`-returning actions out of the box. +**** === Stateful Fault Tolerance Strategies @@ -208,17 +201,15 @@ That is, they hold some state required for their correct functioning, such as th If you use these strategies, you have to consider their lifecycle. The {smallrye-fault-tolerance} programmatic API makes such reasoning pretty straightforward. -Each `FaultTolerance` object has its own instance of each fault tolerance strategy, including the stateful strategies. -If you use a single `FaultTolerance` object for guarding multiple different actions, all those actions will be guarded by the same bulkhead, circuit breaker and/or rate limit. -If, on the other hand, you use different `FaultTolerance` objects for guarding different actions, each action will be guarded by its own bulkhead, circuit breaker and/or rate limit. +Each `Guard` object has its own instance of each fault tolerance strategy, including the stateful strategies. +If you use a single `Guard` object for guarding multiple different actions, all those actions will be guarded by the same bulkhead, circuit breaker and/or rate limit. +If, on the other hand, you use different `Guard` objects for guarding different actions, each action will be guarded by its own bulkhead, circuit breaker and/or rate limit. -If you use the `adapt*` methods, the resulting `Callable`, `Supplier` or `Runnable` objects will guard the underlying action using the original `FaultTolerance` instance, so stateful strategies will be shared. - -If you use the `create*` methods that directly return `Callable`, `Supplier` or `Runnable`, each such creation will have its own `FaultTolerance` instance under the hood, so stateful strategies will _not_ be shared. +If you call the `adapt*` methods on the same `Guard` multiple times, the resulting `Callable` or `Supplier` objects will guard the underlying action using the original `Guard` instance, so stateful strategies will be shared. === Circuit Breaker Maintenance -The `CircuitBreakerMaintenance` API, accessed through `FaultTolerance.circuitBreakerMaintenance()` or by injection in the CDI implementation, can be used to manipulate all named circuit breakers. +The `CircuitBreakerMaintenance` API, accessed through `CircuitBreakerMaintenance.get()` or by injection in the CDI implementation, can be used to manipulate all named circuit breakers. A circuit breaker is given a name by calling `withCircuitBreaker().name("\...")` on the fault tolerance builder, or using the `@CircuitBreakerName` annotation in the declarative API. Additionally, `CircuitBreakerMaintenance.resetAll()` will also reset all unnamed circuit breakers declared using the `@CicruitBreaker` annotation. @@ -239,121 +230,171 @@ For example: [source,java] ---- -private static final FaultTolerance guard = FaultTolerance.create() +private static final Guard GUARD = Guard.create() .withTimeout().duration(5, ChronoUnit.SECONDS).onTimeout(() -> ...).done() // <1> .build(); ---- -<1> The `onTimeout` method takes a `Runnable` that will later be executed whenever an invocation guarded by `guard` times out. +<1> The `onTimeout` method takes a `Runnable` that will later be executed whenever an invocation guarded by `GUARD` times out. All event listeners registered like this must run quickly and must not throw exceptions. -=== Summary of `FaultTolerance` Methods +== Configuration -There's a number of static `create*` methods on the `FaultTolerance` interface. -Which one do you want to call depends on the result type of the builder and whether the guarded actions are synchronous or asynchronous. +As mentioned above, with the single exception of `MP_Fault_Tolerance_NonFallback_Enabled`, there is no support for external configuration of fault tolerance strategies. +This may change in the future, though possibly only in the CDI implementation. -|=== -| The builder result type | Synchronous actions | Asynchronous actions +== Metrics -| `FaultTolerance` -| `create()` -> `FaultTolerance` -| `createAsync()` -> `FaultTolerance>` +The programmatic API is integrated with metrics. +All metrics, as described xref:reference/metrics.adoc[in the Metrics reference guide] and the linked guides, are supported. +The only difference is the value of the `method` tag. +With the programmatic API, the `method` tag will be set to the _description_ of the guarded operation, provided on the `Guard` builder. -| `Callable` -| `createCallable(Callable)` -> `Callable` -| `createAsyncCallable(Callable>)` -> `Callable>` +[source,java] +---- +private static final Guard GUARD = Guard.create() + .withDescription("hello") // <1> + .withRetry().maxRetries(3).done() + .build(); +---- -| `Supplier` -| `createSupplier(Supplier)` -> `Supplier` -| `createAsyncSupplier(Supplier>)` -> `Supplier>` +<1> A description of `hello` is set, it will be used as a value of the `method` tag in all metrics. -| `Runnable` -| `createRunnable(Runnable)` -> `Runnable` -| `createAsyncRunnable(Runnable)` -> `Runnable` -|=== +It is possible to create multiple `Guard` objects with the same description. +In this case, it won't be possible to distinguish the different `Guard` objects in metrics; their values will be aggregated. -When you have an instance of `FaultTolerance`, there's also a number of instance methods that either execute an action, or adapt an unguarded action to a guarded one. -Which one do you want to call depends on the type used to represent the action. +If no description is provided, a random UUID is used. -|=== -| The action type | Execute | Adapt +== Integration Concerns -| `Callable` -| `call(Callable)` -> `T` -| `adaptCallable(Callable)` -> `Callable` +Integration concerns, which are particularly interesting for users of the standalone implementation, are xref:integration/programmatic-api.adoc[described] in the integration section. -| `Supplier` -| `get(Supplier)` -> `T` -| `adaptSupplier(Supplier)` -> `Supplier` +[[migration_from_faulttolerance]] +== Migration from `FaultTolerance` -| `Runnable` -| `run(Runnable)` -> `void` -| `adaptRunnable(Runnable)` -> `Runnable` -|=== +The 1st version of the programmatic API had the `FaultTolerance` interface. +This is deprecated and scheduled for removal in {smallrye-fault-tolerance} 7.0. +The replacement are the `Guard` and `TypedGuard` types. -== Mutiny Support +When migrating, one first has to decide which type to use as a replacement. +The `TypedGuard` is much closer to the `FaultTolerance` type, so that is the easiest way to migrate. +There are some differences still: -In addition to the `FaultTolerance` interface, which provides support for guarding synchronous actions and asynchronous actions using `CompletionStage`, there's a special programmatic API entrypoint for asynchronous actions using the Mutiny library. -It is enough to include the Mutiny support library `io.smallrye:smallrye-fault-tolerance-mutiny`, as described in xref:reference/asynchronous.adoc#async-types[Additional Asynchronous Types]. +- the `create()` static method now takes a parameter that expresses the guarded action type, either as a `Class` or as a `TypeLiteral`; +- there are no `cast()` and `castAsync()` methods, because `TypedGuard` only guards actions of a single type; +- there is no support for guarding or adapting ``Runnable``s, only ``Callable``s and ``Supplier``s are supported. + As a replacement for `Runnable`, a `Supplier` (or `Supplier>` etc.) can be used. -This entrypoint is called `MutinyFaultTolerance` and it includes static factory methods for creating a `Callable>`, `Supplier` and `FaultTolerance>`. -Guarding a `Multi` is not supported. +For example, these usages of `FaultTolerance`: -These factory methods return the common fault tolerance builder, which is supposed to be used just like the builder used when guarding an async action of type `CompletionStage`. -For example: +[source,java] +---- +static final FaultTolerance FT1 = FaultTolerance.create() + .withDescription("ft1") + .withRetry().maxRetries(3).done() + .withFallback().handler(() -> "fallback").done() + .build(); + +static final FaultTolerance> FT2 = FaultTolerance.createAsync() + .withDescription("ft2") + .withBulkhead().limit(5).queueSize(100).done() + .withTimeout().duration(3, ChronoUnit.SECONDS).done() + .build(); +---- + +can be rewritten to: [source,java] ---- -public class MyService { - private final Supplier> guard = MutinyFaultTolerance.createSupplier(() -> externalService.hello()) // <1> - .withTimeout().duration(5, ChronoUnit.SECONDS).done() - .withFallback().handler(() -> Uni.createFrom().item("fallback")).done() +static final TypedGuard GUARD1 = TypedGuard.create(String.class) + .withDescription("ft1") + .withRetry().maxRetries(3).done() + .withFallback().handler(() -> "fallback").done() .build(); - public Uni hello() { - return guard.get(); - } -} +static final TypedGuard> GUARD2 = TypedGuard.create( + new TypeLiteral>() {}) + .withDescription("ft2") + .withBulkhead().limit(5).queueSize(100).done() + .withTimeout().duration(3, ChronoUnit.SECONDS).done() + .build(); ---- -<1> The call to `externalService.hello()` is supposed to return `Uni`. +After creating a `TypedGuard`, there's no change in how it's used. +The methods `call()`, `get()`, `adaptCallable()` and `adaptSupplier()` have the same signatures as before. -Note that the `Uni` type is lazy, so the action itself won't execute until the guarded `Uni` is subscribed to. +`TypedGuard` is the only possible replacement if you need fallback. +If you don't need fallback, and especially if you need to guard actions of multiple types, `Guard` is a better choice. -.Quarkus -**** -In Quarkus, the Mutiny support library is present by default. -You can use `MutinyFaultTolerance` out of the box. -**** +The differences are: -== Configuration +- `Guard` has no type parameter (is not generic) and does not allow defining fallback; +- there are no `cast()` and `castAsync()` methods, because they are no longer needed; +- there is no support for guarding or adapting ``Runnable``s, only ``Callable``s and ``Supplier``s are supported. + As a replacement for `Runnable`, a `Supplier` (or `Supplier>` etc.) can be used. -As mentioned above, with the single exception of `MP_Fault_Tolerance_NonFallback_Enabled`, there is no support for external configuration of fault tolerance strategies. -This may change in the future, though possibly only in the CDI implementation. +For example, these usages of `FaultTolerance`: -== Metrics +[source,java] +---- +static final FaultTolerance FT1 = FaultTolerance.create() + .withDescription("ft1") + .withRetry().maxRetries(3).done() + .withCircuitBreaker().done() + .build(); -The programmatic API is integrated with metrics. -All metrics, as described xref:reference/metrics.adoc[in the Metrics reference guide] and the linked guides, are supported. -The only difference is the value of the `method` tag. -With the programmatic API, the `method` tag will be set to the _description_ of the guarded operation, provided on the `FaultTolerance` builder. +static final FaultTolerance> FT2 = FaultTolerance.createAsync() + .withDescription("ft2") + .withBulkhead().limit(5).queueSize(100).done() + .withTimeout().duration(3, ChronoUnit.SECONDS).done() + .build(); +---- + +can be rewritten to: [source,java] ---- -private static final FaultTolerance guarded = FaultTolerance.create() - .withDescription("hello") // <1> - .withFallback().handler(() -> "fallback").done() - .build(); +static final Guard GUARD1 = Guard.create() + .withDescription("ft1") + .withRetry().maxRetries(3).done() + .withCircuitBreaker().done() + .build(); + +static final Guard GUARD2 = Guard.create() + .withDescription("ft2") + .withBulkhead().limit(5).queueSize(100).done() + .withTimeout().duration(3, ChronoUnit.SECONDS).done() + .build(); ---- -<1> A description of `hello` is set, it will be used as a value of the `method` tag in all metrics. +Now, as mentioned above, `Guard` is used slightly differently than `FaultTolerance`. +The methods `call()`, `get()`, `adaptCallable()` and `adaptSupplier()` take an extra parameter that expresses the type of the guarded action, again either as a `Class` or as a `TypeLiteral`. -It is possible to create multiple `FaultTolerance` objects with the same description. -In this case, it won't be possible to distinguish the different `FaultTolerance` objects in metrics; their values will be aggregated. +For example, these usages of `FaultTolerance`: -If no description is provided, a random UUID is used. +[source,java] +---- +String result1 = FT1.call(() -> externalService.hello()); +CompletionStage result2 = FT2.get(() -> externalService.helloAsync()); -== Integration Concerns +Callable callable = FT1.adaptCallable( + () -> externalService.hello()); +Supplier> supplier = FT2.adaptSupplier( + () -> externalService.helloAsync()); +---- -Integration concerns, which are particularly interesting for users of the standalone implementation, are xref:integration/programmatic-api.adoc[described] in the integration section. +can be rewritten to: + +[source,java] +---- +String result1 = GUARD1.call(() -> externalService.hello(), String.class); +CompletionStage result2 = GUARD2.get(() -> externalService.helloAsync(), + new TypeLiteral>() {}); + +Callable callable = GUARD1.adaptCallable( + () -> externalService.hello(), String.class); +Supplier> supplier = GUARD2.adaptSupplier( + () -> externalService.helloAsync(), + new TypeLiteral>() {}); +---- diff --git a/doc/modules/ROOT/pages/reference/reusable.adoc b/doc/modules/ROOT/pages/reference/reusable.adoc index 86d21aa8..41d46325 100644 --- a/doc/modules/ROOT/pages/reference/reusable.adoc +++ b/doc/modules/ROOT/pages/reference/reusable.adoc @@ -6,10 +6,10 @@ The declarative, annotation-based API of {microprofile-fault-tolerance} doesn't In a single class, the configuration may be shared across all methods by putting the annotations on the class instead of individual methods, but even then, _stateful_ fault tolerance strategies are not shared. Each method has its own bulkhead, circuit breaker and/or rate limit, which is often not what you want. -The xref:reference/programmatic-api.adoc[programmatic API] of {smallrye-fault-tolerance} allows using a single `FaultTolerance` object to guard multiple disparate actions, which allows reuse and state sharing. -It is possible to use a programmatically constructed `FaultTolerance` object declaratively, using the `@ApplyFaultTolerance` annotation. +The xref:reference/programmatic-api.adoc[programmatic API] of {smallrye-fault-tolerance} allows using a single `Guard` or `TypedGuard` object to guard multiple disparate actions, which allows reuse and state sharing. +It is possible to use a programmatically constructed `Guard` or `TypedGuard` object declaratively, using the `@ApplyGuard` annotation. -To be able to do that, we need a bean of type `FaultTolerance` with the `@Identifier` qualifier: +To be able to do that, we need a bean of type `Guard` with the `@Identifier` qualifier: [source,java] ---- @@ -17,46 +17,58 @@ To be able to do that, we need a bean of type `FaultTolerance` with the `@Identi public class PreconfiguredFaultTolerance { @Produces @Identifier("my-fault-tolerance") - public static final FaultTolerance FT = FaultTolerance.create() + public static final Guard GUARD = Guard.create() .withRetry().maxRetries(2).done() - .withFallback().handler(() -> "fallback").done() + .withTimeout().done() .build(); } ---- -See the xref:reference/programmatic-api.adoc[programmatic API] documentation for more information about creating the `FaultTolerance` instance. +See the xref:reference/programmatic-api.adoc[programmatic API] documentation for more information about creating the `Guard` or `TypedGuard` instance. It is customary to create the bean by declaring a `static` producer field, just like in the previous example. -Once we have that, we can apply `my-fault-tolerance` to synchronous methods that return `String`: +Once we have that, we can apply `my-fault-tolerance` to any method: [source,java] ---- @ApplicationScoped public class MyService { - @ApplyFaultTolerance("my-fault-tolerance") + @ApplyGuard("my-fault-tolerance") public String doSomething() { ... } + + @ApplyGuard("my-fault-tolerance") + public CompletionStage doSomethingElse() { + ... + } } ---- -It is also possible to create a bean of type `FaultTolerance` and apply it to synchronous methods that return different types. -Note that this effectively precludes defining a useful fallback, because fallback can only be defined when the value type is known. - -It is also possible to define a bean of type `FaultTolerance>` and apply it to asynchronous methods that return `CompletionStage`. -Likewise, it is possible to do this for xref:reference/asynchronous.adoc#async-types[additional asynchronous types]. +Note that it is not possible to define a fallback on `Guard`, because fallback is tied to the action type. -Note that you can't define a synchronous `FaultTolerance` object and apply it to any asynchronous method. -Similarly, you can't define an asynchronous `FaultTolerance>` and apply it to a synchronous method or an asynchronous method with different asynchronous type. -This limitation will be lifted in the future. +It is also possible to create a bean of type `TypedGuard<>` and apply it to methods that return the type `TypedGuard` was created with. +Note that `TypedGuard` allows defining a fallback, because it can only be used to guard methods with a single return type. == Metrics -Methods annotated `@ApplyFaultTolerance` gather metrics similarly to methods annotated with {microprofile-fault-tolerance} annotations. +Methods annotated `@ApplyGuard` gather metrics similarly to methods annotated with {microprofile-fault-tolerance} annotations. That is, each method gets its own metrics, with the `method` tag being `.`. At the same time, state is still shared. -All methods annotated `@ApplyFaultTolerance` share the same bulkhead, circuit breaker and/or rate limit. +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. + +[[migration_from_applyfaulttolerance]] +== Migration from `@ApplyFaultTolerance` + +The 1st version of the programmatic API had the `@ApplyFaultTolerance` annotation. +That annotation is deprecated and scheduled for removal in {smallrye-fault-tolerance} 7.0. + +To migrate, replace `@ApplyFaultTolerance` with `@ApplyGuard` and change the `FaultTolerance<>` producers to produce `Guard` or `TypedGuard<>`. +See xref:reference/programmatic-api.adoc#migration_from_faulttolerance[the programmatic API migration guide] for more details about that. -If the `FaultTolerance` object used for `@ApplyFaultTolerance` is also used xref:reference/programmatic-api.adoc[programmatically], that usage is coalesced in metrics under the description as the `method` tag. +Note that it is not possible to define both `Guard` and `TypedGuard<>` with the same identifier; that leads to a deployment problem. +Therefore, for each producer of `FaultTolerance<>`, you have to decide whether the replacement should be `Guard` or `TypedGuard<>`. diff --git a/implementation/autoconfig/core/src/main/java/io/smallrye/faulttolerance/autoconfig/FaultToleranceMethod.java b/implementation/autoconfig/core/src/main/java/io/smallrye/faulttolerance/autoconfig/FaultToleranceMethod.java index 9d123a8e..9df6d44a 100644 --- a/implementation/autoconfig/core/src/main/java/io/smallrye/faulttolerance/autoconfig/FaultToleranceMethod.java +++ b/implementation/autoconfig/core/src/main/java/io/smallrye/faulttolerance/autoconfig/FaultToleranceMethod.java @@ -14,6 +14,7 @@ import io.smallrye.common.annotation.Blocking; import io.smallrye.common.annotation.NonBlocking; import io.smallrye.faulttolerance.api.ApplyFaultTolerance; +import io.smallrye.faulttolerance.api.ApplyGuard; import io.smallrye.faulttolerance.api.AsynchronousNonBlocking; import io.smallrye.faulttolerance.api.BeforeRetry; import io.smallrye.faulttolerance.api.CircuitBreakerName; @@ -43,6 +44,7 @@ public class FaultToleranceMethod { public MethodDescriptor method; public ApplyFaultTolerance applyFaultTolerance; + public ApplyGuard applyGuard; public Asynchronous asynchronous; public AsynchronousNonBlocking asynchronousNonBlocking; @@ -80,6 +82,7 @@ public boolean isLegitimate() { // do _not_ trigger the fault tolerance interceptor alone, only in combination // with other fault tolerance annotations return applyFaultTolerance != null + || applyGuard != null || asynchronous != null || asynchronousNonBlocking != null || bulkhead != null diff --git a/implementation/core/src/main/java/io/smallrye/faulttolerance/core/apiimpl/BasicMeteredOperationImpl.java b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/apiimpl/BasicMeteredOperationImpl.java index 982fbe97..09b0ecf2 100644 --- a/implementation/core/src/main/java/io/smallrye/faulttolerance/core/apiimpl/BasicMeteredOperationImpl.java +++ b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/apiimpl/BasicMeteredOperationImpl.java @@ -4,7 +4,7 @@ final class BasicMeteredOperationImpl implements MeteredOperation { private final String name; - private final boolean isAsynchronous; + private final boolean mayBeAsynchronous; private final boolean hasBulkhead; private final boolean hasCircuitBreaker; private final boolean hasFallback; @@ -12,10 +12,10 @@ final class BasicMeteredOperationImpl implements MeteredOperation { private final boolean hasRetry; private final boolean hasTimeout; - BasicMeteredOperationImpl(String name, boolean isAsynchronous, boolean hasBulkhead, boolean hasCircuitBreaker, + BasicMeteredOperationImpl(String name, boolean mayBeAsynchronous, boolean hasBulkhead, boolean hasCircuitBreaker, boolean hasFallback, boolean hasRateLimit, boolean hasRetry, boolean hasTimeout) { this.name = name; - this.isAsynchronous = isAsynchronous; + this.mayBeAsynchronous = mayBeAsynchronous; this.hasBulkhead = hasBulkhead; this.hasCircuitBreaker = hasCircuitBreaker; this.hasFallback = hasFallback; @@ -25,8 +25,8 @@ final class BasicMeteredOperationImpl implements MeteredOperation { } @Override - public boolean isAsynchronous() { - return isAsynchronous; + public boolean mayBeAsynchronous() { + return mayBeAsynchronous; } @Override diff --git a/implementation/core/src/main/java/io/smallrye/faulttolerance/core/apiimpl/FaultToleranceImpl.java b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/apiimpl/FaultToleranceImpl.java index eba083fb..1fb2f3d6 100644 --- a/implementation/core/src/main/java/io/smallrye/faulttolerance/core/apiimpl/FaultToleranceImpl.java +++ b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/apiimpl/FaultToleranceImpl.java @@ -53,7 +53,6 @@ import io.smallrye.faulttolerance.core.retry.TimerDelay; import io.smallrye.faulttolerance.core.stopwatch.SystemStopwatch; import io.smallrye.faulttolerance.core.timeout.Timeout; -import io.smallrye.faulttolerance.core.util.DirectExecutor; import io.smallrye.faulttolerance.core.util.ExceptionDecision; import io.smallrye.faulttolerance.core.util.Preconditions; import io.smallrye.faulttolerance.core.util.PredicateBasedExceptionDecision; @@ -67,6 +66,7 @@ // // in synchronous scenario, V = T // in asynchronous scenario, T is an async type that eventually produces V +@Deprecated(forRemoval = true) public final class FaultToleranceImpl implements FaultTolerance { private final FaultToleranceStrategy strategy; private final AsyncSupport asyncSupport; @@ -385,10 +385,10 @@ private FaultToleranceStrategy buildAsyncStrategy(BuilderLazyDependencies FaultToleranceStrategy result = invocation(); // thread offload is always enabled - Executor executor = offloadToAnotherThread - ? (offloadExecutor != null ? offloadExecutor : lazyDependencies.asyncExecutor()) - : DirectExecutor.INSTANCE; - result = new ThreadOffload<>(result, executor); + if (offloadToAnotherThread) { + Executor executor = offloadExecutor != null ? offloadExecutor : lazyDependencies.asyncExecutor(); + result = new ThreadOffload<>(result, executor); + } if (lazyDependencies.ftEnabled() && bulkheadBuilder != null) { result = new Bulkhead<>(result, description, diff --git a/implementation/core/src/main/java/io/smallrye/faulttolerance/core/apiimpl/GuardCommon.java b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/apiimpl/GuardCommon.java new file mode 100644 index 00000000..f891112a --- /dev/null +++ b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/apiimpl/GuardCommon.java @@ -0,0 +1,69 @@ +package io.smallrye.faulttolerance.core.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 io.smallrye.faulttolerance.core.FaultToleranceContext; +import io.smallrye.faulttolerance.core.FaultToleranceStrategy; +import io.smallrye.faulttolerance.core.Future; +import io.smallrye.faulttolerance.core.invocation.AsyncSupport; +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.metrics.MeteredOperationName; + +final class GuardCommon { + // V = value type, e.g. String + // T = result type, e.g. String or CompletionStage or Uni + // + // in synchronous scenario, V = T + // in asynchronous scenario, T is an async type that eventually produces V + static AsyncSupport asyncSupport(Type type) { + if (type instanceof Class) { + return AsyncSupportRegistry.get(new Class[0], (Class) type); + } else if (type instanceof ParameterizedType) { + ParameterizedType parameterizedType = (ParameterizedType) type; + Class rawType = (Class) parameterizedType.getRawType(); + return AsyncSupportRegistry.get(new Class[0], rawType); + } else { + return null; + } + } + + // V = value type, e.g. String + // T = result type, e.g. String or CompletionStage or Uni + // + // in synchronous scenario, V = T + // in asynchronous scenario, T is an async type that eventually produces V + static T guard(Callable action, FaultToleranceStrategy strategy, AsyncSupport asyncSupport, + EventHandlers eventHandlers, MeteredOperationName meteredOperationName) throws Exception { + if (asyncSupport == null) { + FaultToleranceContext ctx = new FaultToleranceContext<>(() -> Future.from(action), false); + if (meteredOperationName != null) { + ctx.set(MeteredOperationName.class, meteredOperationName); + } + eventHandlers.register(ctx); + try { + FaultToleranceStrategy castStrategy = (FaultToleranceStrategy) strategy; + return castStrategy.apply(ctx).awaitBlocking(); + } catch (Exception e) { + throw e; + } catch (Throwable e) { + throw sneakyThrow(e); + } + } + + Invoker invoker = new CallableInvoker<>(action); + FaultToleranceContext ctx = new FaultToleranceContext<>(() -> asyncSupport.toFuture(invoker), true); + ctx.set(AsyncSupport.class, asyncSupport); + if (meteredOperationName != null) { + ctx.set(MeteredOperationName.class, meteredOperationName); + } + eventHandlers.register(ctx); + Invoker> wrapper = new StrategyInvoker<>(null, strategy, ctx); + return asyncSupport.fromFuture(wrapper); + } +} diff --git a/implementation/core/src/main/java/io/smallrye/faulttolerance/core/apiimpl/GuardImpl.java b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/apiimpl/GuardImpl.java new file mode 100644 index 00000000..35f822bb --- /dev/null +++ b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/apiimpl/GuardImpl.java @@ -0,0 +1,859 @@ +package io.smallrye.faulttolerance.core.apiimpl; + +import static io.smallrye.faulttolerance.core.Invocation.invocation; +import static io.smallrye.faulttolerance.core.util.Durations.timeInMillis; +import static io.smallrye.faulttolerance.core.util.SneakyThrow.sneakyThrow; + +import java.lang.reflect.Type; +import java.time.temporal.ChronoUnit; +import java.util.Collection; +import java.util.Collections; +import java.util.UUID; +import java.util.concurrent.Callable; +import java.util.concurrent.Executor; +import java.util.function.Consumer; +import java.util.function.Predicate; +import java.util.function.Supplier; + +import jakarta.enterprise.util.TypeLiteral; + +import io.smallrye.faulttolerance.api.CircuitBreakerState; +import io.smallrye.faulttolerance.api.CustomBackoffStrategy; +import io.smallrye.faulttolerance.api.Guard; +import io.smallrye.faulttolerance.api.RateLimitType; +import io.smallrye.faulttolerance.core.FaultToleranceStrategy; +import io.smallrye.faulttolerance.core.async.RememberEventLoop; +import io.smallrye.faulttolerance.core.async.SyncAsyncSplit; +import io.smallrye.faulttolerance.core.async.ThreadOffload; +import io.smallrye.faulttolerance.core.bulkhead.Bulkhead; +import io.smallrye.faulttolerance.core.circuit.breaker.CircuitBreaker; +import io.smallrye.faulttolerance.core.circuit.breaker.CircuitBreakerEvents; +import io.smallrye.faulttolerance.core.invocation.AsyncSupport; +import io.smallrye.faulttolerance.core.metrics.DelegatingMetricsCollector; +import io.smallrye.faulttolerance.core.metrics.MeteredOperation; +import io.smallrye.faulttolerance.core.metrics.MeteredOperationName; +import io.smallrye.faulttolerance.core.metrics.MetricsProvider; +import io.smallrye.faulttolerance.core.rate.limit.RateLimit; +import io.smallrye.faulttolerance.core.retry.BackOff; +import io.smallrye.faulttolerance.core.retry.ConstantBackOff; +import io.smallrye.faulttolerance.core.retry.CustomBackOff; +import io.smallrye.faulttolerance.core.retry.ExponentialBackOff; +import io.smallrye.faulttolerance.core.retry.FibonacciBackOff; +import io.smallrye.faulttolerance.core.retry.Jitter; +import io.smallrye.faulttolerance.core.retry.RandomJitter; +import io.smallrye.faulttolerance.core.retry.Retry; +import io.smallrye.faulttolerance.core.retry.ThreadSleepDelay; +import io.smallrye.faulttolerance.core.retry.TimerDelay; +import io.smallrye.faulttolerance.core.stopwatch.SystemStopwatch; +import io.smallrye.faulttolerance.core.timeout.Timeout; +import io.smallrye.faulttolerance.core.util.ExceptionDecision; +import io.smallrye.faulttolerance.core.util.Preconditions; +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; + +public class GuardImpl implements Guard { + final FaultToleranceStrategy strategy; + final EventHandlers eventHandlers; + + // Circuit breakers created using the programmatic API are registered with `CircuitBreakerMaintenance` + // in two phases: + // + // 1. The name is registered eagerly, during `BuilderImpl.build()`, so that `CircuitBreakerMaintenance` methods + // may be called immediately after building the `Guard` instance. A single name may be registered + // multiple times. + // 2. The circuit breaker instance is registered lazily, during `BuilderImpl.buildStrategy()`, so that + // a `Guard` instance that is created but never used doesn't prevent an actually useful + // circuit breaker from being registered later. Only one circuit breaker may be registered + // for given name. + // + // Lazy registration of circuit breakers exists to allow normal-scoped CDI beans to declare `Guard` + // fields that are assigned inline (which effectively means in a constructor). Normal-scoped beans always + // have a client proxy, whose class inherits from the bean class and calls the superclass's zero-parameter + // constructor. This leads to the client proxy instance also having an instance of `Guard`, + // but that instance is never used. The useful `Guard` instance is held by the actual bean instance, + // which is created lazily, on the first method invocation on the client proxy. + + GuardImpl(FaultToleranceStrategy strategy, EventHandlers eventHandlers) { + this.strategy = strategy; + this.eventHandlers = eventHandlers; + } + + public T guard(Callable action, Type valueType, MeteredOperationName meteredOperationName) throws Exception { + AsyncSupport asyncSupport = GuardCommon.asyncSupport(valueType); + return GuardCommon.guard(action, (FaultToleranceStrategy) strategy, asyncSupport, eventHandlers, + meteredOperationName); + } + + @Override + public T call(Callable action, Class type) throws Exception { + return guard(action, type, null); + } + + @Override + public T call(Callable action, TypeLiteral type) throws Exception { + return guard(action, type.getType(), null); + } + + @Override + public T get(Supplier action, Class type) { + try { + return guard(action::get, type, null); + } catch (Exception e) { + throw sneakyThrow(e); + } + } + + @Override + public T get(Supplier action, TypeLiteral type) { + try { + return guard(action::get, type.getType(), null); + } catch (Exception e) { + throw sneakyThrow(e); + } + } + + public static class BuilderImpl implements Builder { + private final BuilderEagerDependencies eagerDependencies; + private final Supplier lazyDependencies; + private String description; + private BulkheadBuilderImpl bulkheadBuilder; + private CircuitBreakerBuilderImpl circuitBreakerBuilder; + private RateLimitBuilderImpl rateLimitBuilder; + private RetryBuilderImpl retryBuilder; + private TimeoutBuilderImpl timeoutBuilder; + private boolean offloadToAnotherThread; + private Executor offloadExecutor; + + public BuilderImpl(BuilderEagerDependencies eagerDependencies, Supplier lazyDependencies) { + this.eagerDependencies = eagerDependencies; + this.lazyDependencies = lazyDependencies; + + this.description = UUID.randomUUID().toString(); + } + + @Override + public Builder withDescription(String value) { + this.description = Preconditions.checkNotNull(value, "Description must be set"); + return this; + } + + @Override + public BulkheadBuilder withBulkhead() { + return new BulkheadBuilderImpl(this); + } + + @Override + public CircuitBreakerBuilder withCircuitBreaker() { + return new CircuitBreakerBuilderImpl(this); + } + + @Override + public RateLimitBuilder withRateLimit() { + return new RateLimitBuilderImpl(this); + } + + @Override + public RetryBuilder withRetry() { + return new RetryBuilderImpl(this); + } + + @Override + public TimeoutBuilder withTimeout() { + return new TimeoutBuilderImpl(this); + } + + @Override + public Builder withThreadOffload(boolean value) { + this.offloadToAnotherThread = value; + return this; + } + + @Override + public Builder withThreadOffloadExecutor(Executor executor) { + this.offloadExecutor = Preconditions.checkNotNull(executor, "Thread offload executor must be set"); + return this; + } + + @Override + public Guard build() { + eagerInitialization(); + return new LazyGuard(() -> new GuardImpl(buildStrategy(lazyDependencies.get()), buildEventHandlers())); + } + + // must not access lazyDependencies + final void eagerInitialization() { + if (circuitBreakerBuilder != null && circuitBreakerBuilder.name != null) { + eagerDependencies.cbMaintenance().registerName(circuitBreakerBuilder.name); + } + } + + final EventHandlers buildEventHandlers() { + Consumer cbMaintenanceEventHandler = null; + if (circuitBreakerBuilder != null && circuitBreakerBuilder.name != null) { + cbMaintenanceEventHandler = eagerDependencies.cbMaintenance() + .stateTransitionEventHandler(circuitBreakerBuilder.name); + } + return new EventHandlers( + bulkheadBuilder != null ? bulkheadBuilder.onAccepted : null, + bulkheadBuilder != null ? bulkheadBuilder.onRejected : null, + bulkheadBuilder != null ? bulkheadBuilder.onFinished : null, + cbMaintenanceEventHandler, + circuitBreakerBuilder != null ? circuitBreakerBuilder.onStateChange : null, + circuitBreakerBuilder != null ? circuitBreakerBuilder.onSuccess : null, + circuitBreakerBuilder != null ? circuitBreakerBuilder.onFailure : null, + circuitBreakerBuilder != null ? circuitBreakerBuilder.onPrevented : null, + rateLimitBuilder != null ? rateLimitBuilder.onPermitted : null, + rateLimitBuilder != null ? rateLimitBuilder.onRejected : null, + retryBuilder != null ? retryBuilder.onRetry : null, + retryBuilder != null ? retryBuilder.onSuccess : null, + retryBuilder != null ? retryBuilder.onFailure : null, + timeoutBuilder != null ? timeoutBuilder.onTimeout : null, + timeoutBuilder != null ? timeoutBuilder.onFinished : null); + } + + final FaultToleranceStrategy buildStrategy(BuilderLazyDependencies lazyDependencies) { + FaultToleranceStrategy result = invocation(); + + // thread offload is always enabled + if (offloadToAnotherThread) { + Executor executor = offloadExecutor != null ? offloadExecutor : lazyDependencies.asyncExecutor(); + result = new SyncAsyncSplit<>(new ThreadOffload<>(result, executor), result); + } + + if (lazyDependencies.ftEnabled() && bulkheadBuilder != null) { + result = new Bulkhead<>(result, description, + bulkheadBuilder.limit, + bulkheadBuilder.queueSize, + bulkheadBuilder.syncQueueingEnabled); + } + + if (lazyDependencies.ftEnabled() && timeoutBuilder != null) { + result = new Timeout<>(result, description, timeoutBuilder.durationInMillis, + lazyDependencies.timer()); + } + + if (lazyDependencies.ftEnabled() && rateLimitBuilder != null) { + result = new RateLimit<>(result, description, + rateLimitBuilder.maxInvocations, + rateLimitBuilder.timeWindowInMillis, + rateLimitBuilder.minSpacingInMillis, + rateLimitBuilder.type, + SystemStopwatch.INSTANCE); + } + + if (lazyDependencies.ftEnabled() && circuitBreakerBuilder != null) { + result = new CircuitBreaker<>(result, description, + createExceptionDecision(circuitBreakerBuilder.skipOn, circuitBreakerBuilder.failOn, + circuitBreakerBuilder.whenPredicate), + circuitBreakerBuilder.delayInMillis, + circuitBreakerBuilder.requestVolumeThreshold, + circuitBreakerBuilder.failureRatio, + circuitBreakerBuilder.successThreshold, + SystemStopwatch.INSTANCE, + lazyDependencies.timer()); + + if (circuitBreakerBuilder.name != null) { + CircuitBreaker circuitBreaker = (CircuitBreaker) result; + eagerDependencies.cbMaintenance().register(circuitBreakerBuilder.name, circuitBreaker); + } + } + + if (lazyDependencies.ftEnabled() && retryBuilder != null) { + Supplier backoff = prepareRetryBackoff(retryBuilder); + + result = new Retry<>(result, description, + createResultDecision(retryBuilder.whenResultPredicate), + createExceptionDecision(retryBuilder.abortOn, retryBuilder.retryOn, + retryBuilder.whenExceptionPredicate), + retryBuilder.maxRetries, retryBuilder.maxDurationInMillis, + () -> new ThreadSleepDelay(backoff.get()), + () -> new TimerDelay(backoff.get(), lazyDependencies.timer()), + SystemStopwatch.INSTANCE, + retryBuilder.beforeRetry != null ? ctx -> retryBuilder.beforeRetry.accept(ctx.failure) : null); + } + + // no fallback here + + MetricsProvider metricsProvider = lazyDependencies.metricsProvider(); + if (metricsProvider.isEnabled()) { + MeteredOperation defaultOperation = buildMeteredOperation(); + result = new DelegatingMetricsCollector<>(result, metricsProvider, defaultOperation); + } + + // thread offload is always enabled + if (!offloadToAnotherThread) { + result = new SyncAsyncSplit<>(new RememberEventLoop<>(result, lazyDependencies.eventLoop()), result); + } + + return result; + } + + private MeteredOperation buildMeteredOperation() { + return new BasicMeteredOperationImpl(description, true, bulkheadBuilder != null, + circuitBreakerBuilder != null, false, rateLimitBuilder != null, + retryBuilder != null, timeoutBuilder != null); + } + + private static ResultDecision createResultDecision(Predicate whenResultPredicate) { + if (whenResultPredicate != null) { + // the builder API accepts a predicate that returns `true` when a result is considered failure, + // but `[CompletionStage]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(Collection> consideredExpected, + Collection> consideredFailure, Predicate 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(Collection> throwableClasses) { + return throwableClasses == null ? SetOfThrowables.EMPTY : SetOfThrowables.create(throwableClasses); + } + + private static Supplier prepareRetryBackoff(RetryBuilderImpl retryBuilder) { + long jitterMs = retryBuilder.jitterInMillis; + Jitter jitter = jitterMs == 0 ? Jitter.ZERO : new RandomJitter(jitterMs); + + if (retryBuilder.exponentialBackoffBuilder != null) { + int factor = retryBuilder.exponentialBackoffBuilder.factor; + long maxDelay = retryBuilder.exponentialBackoffBuilder.maxDelayInMillis; + return () -> new ExponentialBackOff(retryBuilder.delayInMillis, factor, jitter, maxDelay); + } else if (retryBuilder.fibonacciBackoffBuilder != null) { + long maxDelay = retryBuilder.fibonacciBackoffBuilder.maxDelayInMillis; + return () -> new FibonacciBackOff(retryBuilder.delayInMillis, jitter, maxDelay); + } else if (retryBuilder.customBackoffBuilder != null) { + Supplier strategy = retryBuilder.customBackoffBuilder.strategy; + return () -> { + CustomBackoffStrategy instance = strategy.get(); + instance.init(retryBuilder.delayInMillis); + return new CustomBackOff(instance::nextDelayInMillis); + }; + } else { + return () -> new ConstantBackOff(retryBuilder.delayInMillis, jitter); + } + } + + static class BulkheadBuilderImpl implements BulkheadBuilder { + private final BuilderImpl parent; + + private int limit = 10; + private int queueSize = 10; + private boolean syncQueueingEnabled; + + private Runnable onAccepted; + private Runnable onRejected; + private Runnable onFinished; + + BulkheadBuilderImpl(BuilderImpl parent) { + this.parent = parent; + } + + @Override + public BulkheadBuilder limit(int value) { + this.limit = Preconditions.check(value, value >= 1, "Limit must be >= 1"); + return this; + } + + @Override + public BulkheadBuilder queueSize(int value) { + this.queueSize = Preconditions.check(value, value >= 1, "Queue size must be >= 1"); + return this; + } + + @Override + public BulkheadBuilder enableSynchronousQueueing() { + this.syncQueueingEnabled = true; + return this; + } + + @Override + public BulkheadBuilder onAccepted(Runnable callback) { + this.onAccepted = Preconditions.checkNotNull(callback, "Accepted callback must be set"); + return this; + } + + @Override + public BulkheadBuilder onRejected(Runnable callback) { + this.onRejected = Preconditions.checkNotNull(callback, "Rejected callback must be set"); + return this; + } + + @Override + public BulkheadBuilder onFinished(Runnable callback) { + this.onFinished = Preconditions.checkNotNull(callback, "Finished callback must be set"); + return this; + } + + @Override + public Builder done() { + parent.bulkheadBuilder = this; + return parent; + } + } + + static class CircuitBreakerBuilderImpl implements CircuitBreakerBuilder { + private final BuilderImpl parent; + + private Collection> failOn = Collections.singleton(Throwable.class); + private Collection> skipOn = Collections.emptySet(); + private boolean setBasedExceptionDecisionDefined = false; + private Predicate whenPredicate; + private long delayInMillis = 5000; + private int requestVolumeThreshold = 20; + private double failureRatio = 0.5; + private int successThreshold = 1; + + private String name; // unnamed by default + + private Consumer onStateChange; + private Runnable onSuccess; + private Runnable onFailure; + private Runnable onPrevented; + + CircuitBreakerBuilderImpl(BuilderImpl parent) { + this.parent = parent; + } + + @Override + public CircuitBreakerBuilder failOn(Collection> value) { + this.failOn = Preconditions.checkNotNull(value, "Exceptions considered failure must be set"); + this.setBasedExceptionDecisionDefined = true; + return this; + } + + @Override + public CircuitBreakerBuilder skipOn(Collection> value) { + this.skipOn = Preconditions.checkNotNull(value, "Exceptions considered success must be set"); + this.setBasedExceptionDecisionDefined = true; + return this; + } + + @Override + public CircuitBreakerBuilder when(Predicate value) { + this.whenPredicate = Preconditions.checkNotNull(value, "Exception predicate must be set"); + return this; + } + + @Override + public CircuitBreakerBuilder delay(long value, ChronoUnit unit) { + Preconditions.check(value, value >= 0, "Delay must be >= 0"); + Preconditions.checkNotNull(unit, "Delay unit must be set"); + + this.delayInMillis = timeInMillis(value, unit); + return this; + } + + @Override + public CircuitBreakerBuilder requestVolumeThreshold(int value) { + this.requestVolumeThreshold = Preconditions.check(value, value >= 1, "Request volume threshold must be >= 1"); + return this; + } + + @Override + public CircuitBreakerBuilder failureRatio(double value) { + this.failureRatio = Preconditions.check(value, value >= 0 && value <= 1, "Failure ratio must be >= 0 and <= 1"); + return this; + } + + @Override + public CircuitBreakerBuilder successThreshold(int value) { + this.successThreshold = Preconditions.check(value, value >= 1, "Success threshold must be >= 1"); + return this; + } + + @Override + public CircuitBreakerBuilder name(String value) { + this.name = Preconditions.checkNotNull(value, "Circuit breaker name must be set"); + return this; + } + + @Override + public CircuitBreakerBuilder onStateChange(Consumer callback) { + this.onStateChange = Preconditions.checkNotNull(callback, "On state change callback must be set"); + return this; + } + + @Override + public CircuitBreakerBuilder onSuccess(Runnable callback) { + this.onSuccess = Preconditions.checkNotNull(callback, "On success callback must be set"); + return this; + } + + @Override + public CircuitBreakerBuilder onFailure(Runnable callback) { + this.onFailure = Preconditions.checkNotNull(callback, "On failure callback must be set"); + return this; + } + + @Override + public CircuitBreakerBuilder onPrevented(Runnable callback) { + this.onPrevented = Preconditions.checkNotNull(callback, "On prevented callback must be set"); + return this; + } + + @Override + public Builder done() { + if (whenPredicate != null && setBasedExceptionDecisionDefined) { + throw new IllegalStateException("The when() method may not be combined with failOn() / skipOn()"); + } + + parent.circuitBreakerBuilder = this; + return parent; + } + } + + static class RateLimitBuilderImpl implements RateLimitBuilder { + private final BuilderImpl parent; + + private int maxInvocations = 100; + private long timeWindowInMillis = 1000; + private long minSpacingInMillis = 0; + private RateLimitType type = RateLimitType.FIXED; + + private Runnable onPermitted; + private Runnable onRejected; + + RateLimitBuilderImpl(BuilderImpl parent) { + this.parent = parent; + } + + @Override + public RateLimitBuilder limit(int value) { + this.maxInvocations = Preconditions.check(value, value >= 1, "Rate limit must be >= 1"); + return this; + } + + @Override + public RateLimitBuilder window(long value, ChronoUnit unit) { + Preconditions.check(value, value >= 1, "Time window length must be >= 1"); + Preconditions.checkNotNull(unit, "Time window length unit must be set"); + + this.timeWindowInMillis = timeInMillis(value, unit); + return this; + } + + @Override + public RateLimitBuilder minSpacing(long value, ChronoUnit unit) { + Preconditions.check(value, value >= 0, "Min spacing must be >= 0"); + Preconditions.checkNotNull(unit, "Min spacing unit must be set"); + + this.minSpacingInMillis = timeInMillis(value, unit); + return this; + } + + @Override + public RateLimitBuilder type(RateLimitType value) { + this.type = Preconditions.checkNotNull(value, "Time window type must be set"); + return this; + } + + @Override + public RateLimitBuilder onPermitted(Runnable callback) { + this.onPermitted = Preconditions.checkNotNull(callback, "Permitted callback must be set"); + return this; + } + + @Override + public RateLimitBuilder onRejected(Runnable callback) { + this.onRejected = Preconditions.checkNotNull(callback, "Rejected callback must be set"); + return this; + } + + @Override + public Builder done() { + parent.rateLimitBuilder = this; + return parent; + } + } + + static class RetryBuilderImpl implements RetryBuilder { + private final BuilderImpl parent; + + private int maxRetries = 3; + private long delayInMillis = 0; + private long maxDurationInMillis = 180_000; + private long jitterInMillis = 200; + private Collection> retryOn = Collections.singleton(Exception.class); + private Collection> abortOn = Collections.emptySet(); + private boolean setBasedExceptionDecisionDefined = false; + private Predicate whenExceptionPredicate; + private Predicate whenResultPredicate; + private Consumer beforeRetry; + + private ExponentialBackoffBuilderImpl exponentialBackoffBuilder; + private FibonacciBackoffBuilderImpl fibonacciBackoffBuilder; + private CustomBackoffBuilderImpl customBackoffBuilder; + + private Runnable onRetry; + private Runnable onSuccess; + private Runnable onFailure; + + RetryBuilderImpl(BuilderImpl parent) { + this.parent = parent; + } + + @Override + public RetryBuilder maxRetries(int value) { + this.maxRetries = Preconditions.check(value, value >= -1, "Max retries must be >= -1"); + return this; + } + + @Override + public RetryBuilder delay(long value, ChronoUnit unit) { + Preconditions.check(value, value >= 0, "Delay must be >= 0"); + Preconditions.checkNotNull(unit, "Delay unit must be set"); + + this.delayInMillis = timeInMillis(value, unit); + return this; + } + + @Override + public RetryBuilder maxDuration(long value, ChronoUnit unit) { + Preconditions.check(value, value >= 0, "Max duration must be >= 0"); + Preconditions.checkNotNull(unit, "Max duration unit must be set"); + + this.maxDurationInMillis = timeInMillis(value, unit); + return this; + } + + @Override + public RetryBuilder jitter(long value, ChronoUnit unit) { + Preconditions.check(value, value >= 0, "Jitter must be >= 0"); + Preconditions.checkNotNull(unit, "Jitter unit must be set"); + + this.jitterInMillis = timeInMillis(value, unit); + return this; + } + + @Override + public RetryBuilder retryOn(Collection> value) { + this.retryOn = Preconditions.checkNotNull(value, "Exceptions to retry on must be set"); + this.setBasedExceptionDecisionDefined = true; + return this; + } + + @Override + public RetryBuilder abortOn(Collection> value) { + this.abortOn = Preconditions.checkNotNull(value, "Exceptions to abort retrying on must be set"); + this.setBasedExceptionDecisionDefined = true; + return this; + } + + @Override + public RetryBuilder whenResult(Predicate value) { + this.whenResultPredicate = Preconditions.checkNotNull(value, "Result predicate must be set"); + return this; + } + + @Override + public RetryBuilder whenException(Predicate value) { + this.whenExceptionPredicate = Preconditions.checkNotNull(value, "Exception predicate must be set"); + return this; + } + + @Override + public RetryBuilder beforeRetry(Runnable value) { + Preconditions.checkNotNull(value, "Before retry handler must be set"); + this.beforeRetry = ignored -> value.run(); + return this; + } + + @Override + public RetryBuilder beforeRetry(Consumer value) { + this.beforeRetry = Preconditions.checkNotNull(value, "Before retry handler must be set"); + return this; + } + + @Override + public ExponentialBackoffBuilder withExponentialBackoff() { + return new ExponentialBackoffBuilderImpl(this); + } + + @Override + public FibonacciBackoffBuilder withFibonacciBackoff() { + return new FibonacciBackoffBuilderImpl(this); + } + + @Override + public CustomBackoffBuilder withCustomBackoff() { + return new CustomBackoffBuilderImpl(this); + } + + @Override + public RetryBuilder onRetry(Runnable callback) { + this.onRetry = Preconditions.checkNotNull(callback, "Retry callback must be set"); + return this; + } + + @Override + public RetryBuilder onSuccess(Runnable callback) { + this.onSuccess = Preconditions.checkNotNull(callback, "Success callback must be set"); + return this; + } + + @Override + public RetryBuilder onFailure(Runnable callback) { + this.onFailure = Preconditions.checkNotNull(callback, "Failure callback must be set"); + return this; + } + + @Override + public Builder done() { + if (whenExceptionPredicate != null && setBasedExceptionDecisionDefined) { + throw new IllegalStateException("The whenException() method may not be combined with retryOn()/abortOn()"); + } + + int backoffStrategies = 0; + if (exponentialBackoffBuilder != null) { + backoffStrategies++; + } + if (fibonacciBackoffBuilder != null) { + backoffStrategies++; + } + if (customBackoffBuilder != null) { + backoffStrategies++; + } + if (backoffStrategies > 1) { + throw new IllegalStateException("Only one backoff strategy may be set for retry"); + } + + parent.retryBuilder = this; + return parent; + } + + static class ExponentialBackoffBuilderImpl implements ExponentialBackoffBuilder { + private final RetryBuilderImpl parent; + + private int factor = 2; + private long maxDelayInMillis = 60_000; + + ExponentialBackoffBuilderImpl(RetryBuilderImpl parent) { + this.parent = parent; + } + + @Override + public ExponentialBackoffBuilder factor(int value) { + this.factor = Preconditions.check(value, value >= 1, "Factor must be >= 1"); + return this; + } + + @Override + public ExponentialBackoffBuilder maxDelay(long value, ChronoUnit unit) { + Preconditions.check(value, value >= 0, "Max delay must be >= 0"); + Preconditions.checkNotNull(unit, "Max delay unit must be set"); + + this.maxDelayInMillis = timeInMillis(value, unit); + return this; + } + + @Override + public RetryBuilder done() { + parent.exponentialBackoffBuilder = this; + return parent; + } + } + + static class FibonacciBackoffBuilderImpl implements FibonacciBackoffBuilder { + private final RetryBuilderImpl parent; + + private long maxDelayInMillis = 60_000; + + FibonacciBackoffBuilderImpl(RetryBuilderImpl parent) { + this.parent = parent; + } + + @Override + public FibonacciBackoffBuilder maxDelay(long value, ChronoUnit unit) { + Preconditions.check(value, value >= 0, "Max delay must be >= 0"); + Preconditions.checkNotNull(unit, "Max delay unit must be set"); + + this.maxDelayInMillis = timeInMillis(value, unit); + return this; + } + + @Override + public RetryBuilder done() { + parent.fibonacciBackoffBuilder = this; + return parent; + } + } + + static class CustomBackoffBuilderImpl implements CustomBackoffBuilder { + private final RetryBuilderImpl parent; + + private Supplier strategy; + + CustomBackoffBuilderImpl(RetryBuilderImpl parent) { + this.parent = parent; + } + + @Override + public CustomBackoffBuilder strategy(Supplier value) { + this.strategy = Preconditions.checkNotNull(value, "Custom backoff strategy must be set"); + return this; + } + + @Override + public RetryBuilder done() { + Preconditions.checkNotNull(strategy, "Custom backoff strategy must be set"); + + parent.customBackoffBuilder = this; + return parent; + } + } + } + + static class TimeoutBuilderImpl implements TimeoutBuilder { + private final BuilderImpl parent; + + private long durationInMillis = 1000; + + private Runnable onTimeout; + private Runnable onFinished; + + TimeoutBuilderImpl(BuilderImpl parent) { + this.parent = parent; + } + + @Override + public TimeoutBuilder duration(long value, ChronoUnit unit) { + Preconditions.check(value, value >= 0, "Timeout duration must be >= 0"); + Preconditions.checkNotNull(unit, "Timeout duration unit must be set"); + + this.durationInMillis = timeInMillis(value, unit); + return this; + } + + @Override + public TimeoutBuilder onTimeout(Runnable callback) { + this.onTimeout = Preconditions.checkNotNull(callback, "Timeout callback must be set"); + return this; + } + + @Override + public TimeoutBuilder onFinished(Runnable callback) { + this.onFinished = Preconditions.checkNotNull(callback, "Finished callback must be set"); + return this; + } + + @Override + public Builder done() { + parent.timeoutBuilder = this; + return parent; + } + } + } +} diff --git a/implementation/core/src/main/java/io/smallrye/faulttolerance/core/apiimpl/LazyGuard.java b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/apiimpl/LazyGuard.java new file mode 100644 index 00000000..645fa9bd --- /dev/null +++ b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/apiimpl/LazyGuard.java @@ -0,0 +1,58 @@ +package io.smallrye.faulttolerance.core.apiimpl; + +import java.util.concurrent.Callable; +import java.util.concurrent.locks.ReentrantLock; +import java.util.function.Supplier; + +import jakarta.enterprise.util.TypeLiteral; + +import io.smallrye.faulttolerance.api.Guard; + +public final class LazyGuard implements Guard { + private final Supplier builder; + + private final ReentrantLock lock = new ReentrantLock(); + + private volatile GuardImpl instance; + + LazyGuard(Supplier builder) { + this.builder = builder; + } + + @Override + public T call(Callable action, Class type) throws Exception { + return instance().call(action, type); + } + + @Override + public T call(Callable action, TypeLiteral type) throws Exception { + return instance().call(action, type); + } + + @Override + public T get(Supplier action, Class type) { + return instance().get(action, type); + } + + @Override + public T get(Supplier action, TypeLiteral type) { + return instance().get(action, type); + } + + public GuardImpl instance() { + GuardImpl instance = this.instance; + if (instance == null) { + lock.lock(); + try { + instance = this.instance; + if (instance == null) { + instance = builder.get(); + this.instance = instance; + } + } finally { + lock.unlock(); + } + } + return instance; + } +} diff --git a/implementation/core/src/main/java/io/smallrye/faulttolerance/core/apiimpl/LazyTypedGuard.java b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/apiimpl/LazyTypedGuard.java new file mode 100644 index 00000000..2dcccee4 --- /dev/null +++ b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/apiimpl/LazyTypedGuard.java @@ -0,0 +1,46 @@ +package io.smallrye.faulttolerance.core.apiimpl; + +import java.util.concurrent.Callable; +import java.util.concurrent.locks.ReentrantLock; +import java.util.function.Supplier; + +import io.smallrye.faulttolerance.api.TypedGuard; + +public final class LazyTypedGuard implements TypedGuard { + private final Supplier> builder; + + private final ReentrantLock lock = new ReentrantLock(); + + private volatile TypedGuardImpl instance; + + LazyTypedGuard(Supplier> builder) { + this.builder = builder; + } + + @Override + public T call(Callable action) throws Exception { + return instance().call(action); + } + + @Override + public T get(Supplier action) { + return instance().get(action); + } + + public TypedGuardImpl instance() { + TypedGuardImpl instance = this.instance; + if (instance == null) { + lock.lock(); + try { + instance = this.instance; + if (instance == null) { + instance = builder.get(); + this.instance = instance; + } + } finally { + lock.unlock(); + } + } + return instance; + } +} diff --git a/implementation/core/src/main/java/io/smallrye/faulttolerance/core/apiimpl/TypedGuardImpl.java b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/apiimpl/TypedGuardImpl.java new file mode 100644 index 00000000..b0b9773a --- /dev/null +++ b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/apiimpl/TypedGuardImpl.java @@ -0,0 +1,944 @@ +package io.smallrye.faulttolerance.core.apiimpl; + +import static io.smallrye.faulttolerance.core.Invocation.invocation; +import static io.smallrye.faulttolerance.core.util.Durations.timeInMillis; +import static io.smallrye.faulttolerance.core.util.SneakyThrow.sneakyThrow; + +import java.lang.reflect.Type; +import java.time.temporal.ChronoUnit; +import java.util.Collection; +import java.util.Collections; +import java.util.UUID; +import java.util.concurrent.Callable; +import java.util.concurrent.Executor; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.function.Supplier; + +import io.smallrye.faulttolerance.api.CircuitBreakerState; +import io.smallrye.faulttolerance.api.CustomBackoffStrategy; +import io.smallrye.faulttolerance.api.RateLimitType; +import io.smallrye.faulttolerance.api.TypedGuard; +import io.smallrye.faulttolerance.core.FailureContext; +import io.smallrye.faulttolerance.core.FaultToleranceStrategy; +import io.smallrye.faulttolerance.core.Future; +import io.smallrye.faulttolerance.core.async.RememberEventLoop; +import io.smallrye.faulttolerance.core.async.SyncAsyncSplit; +import io.smallrye.faulttolerance.core.async.ThreadOffload; +import io.smallrye.faulttolerance.core.bulkhead.Bulkhead; +import io.smallrye.faulttolerance.core.circuit.breaker.CircuitBreaker; +import io.smallrye.faulttolerance.core.circuit.breaker.CircuitBreakerEvents; +import io.smallrye.faulttolerance.core.fallback.Fallback; +import io.smallrye.faulttolerance.core.invocation.AsyncSupport; +import io.smallrye.faulttolerance.core.invocation.ConstantInvoker; +import io.smallrye.faulttolerance.core.metrics.DelegatingMetricsCollector; +import io.smallrye.faulttolerance.core.metrics.MeteredOperation; +import io.smallrye.faulttolerance.core.metrics.MeteredOperationName; +import io.smallrye.faulttolerance.core.metrics.MetricsProvider; +import io.smallrye.faulttolerance.core.rate.limit.RateLimit; +import io.smallrye.faulttolerance.core.retry.BackOff; +import io.smallrye.faulttolerance.core.retry.ConstantBackOff; +import io.smallrye.faulttolerance.core.retry.CustomBackOff; +import io.smallrye.faulttolerance.core.retry.ExponentialBackOff; +import io.smallrye.faulttolerance.core.retry.FibonacciBackOff; +import io.smallrye.faulttolerance.core.retry.Jitter; +import io.smallrye.faulttolerance.core.retry.RandomJitter; +import io.smallrye.faulttolerance.core.retry.Retry; +import io.smallrye.faulttolerance.core.retry.ThreadSleepDelay; +import io.smallrye.faulttolerance.core.retry.TimerDelay; +import io.smallrye.faulttolerance.core.stopwatch.SystemStopwatch; +import io.smallrye.faulttolerance.core.timeout.Timeout; +import io.smallrye.faulttolerance.core.util.ExceptionDecision; +import io.smallrye.faulttolerance.core.util.Preconditions; +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; + +// V = value type, e.g. String +// T = result type, e.g. String or CompletionStage or Uni +// +// in synchronous scenario, V = T +// in asynchronous scenario, T is an async type that eventually produces V +public final class TypedGuardImpl implements TypedGuard { + private final FaultToleranceStrategy strategy; + private final AsyncSupport asyncSupport; + private final EventHandlers eventHandlers; + + // Circuit breakers created using the programmatic API are registered with `CircuitBreakerMaintenance` + // in two phases: + // + // 1. The name is registered eagerly, during `BuilderImpl.build()`, so that `CircuitBreakerMaintenance` methods + // may be called immediately after building the `TypedGuard` instance. A single name may be registered + // multiple times. + // 2. The circuit breaker instance is registered lazily, during `BuilderImpl.buildStrategy()`, so that + // a `TypedGuard` instance that is created but never used doesn't prevent an actually useful + // circuit breaker from being registered later. Only one circuit breaker may be registered + // for given name. + // + // Lazy registration of circuit breakers exists to allow normal-scoped CDI beans to declare `TypedGuard` + // fields that are assigned inline (which effectively means in a constructor). Normal-scoped beans always + // have a client proxy, whose class inherits from the bean class and calls the superclass's zero-parameter + // constructor. This leads to the client proxy instance also having an instance of `TypedGuard`, + // but that instance is never used. The useful `TypedGuard` instance is held by the actual bean instance, + // which is created lazily, on the first method invocation on the client proxy. + + TypedGuardImpl(FaultToleranceStrategy strategy, AsyncSupport asyncSupport, EventHandlers eventHandlers) { + this.strategy = strategy; + this.asyncSupport = asyncSupport; + this.eventHandlers = eventHandlers; + } + + public T guard(Callable action, MeteredOperationName meteredOperationName) throws Exception { + return GuardCommon.guard(action, strategy, asyncSupport, eventHandlers, meteredOperationName); + } + + @Override + public T call(Callable action) throws Exception { + return guard(action, null); + } + + @Override + public T get(Supplier action) { + try { + return guard(action::get, null); + } catch (Exception e) { + throw sneakyThrow(e); + } + } + + public static class BuilderImpl implements TypedGuard.Builder { + private final BuilderEagerDependencies eagerDependencies; + private final Supplier lazyDependencies; + private final AsyncSupport asyncSupport; + + private String description; + private BulkheadBuilderImpl bulkheadBuilder; + private CircuitBreakerBuilderImpl circuitBreakerBuilder; + private FallbackBuilderImpl fallbackBuilder; + private RateLimitBuilderImpl rateLimitBuilder; + private RetryBuilderImpl retryBuilder; + private TimeoutBuilderImpl timeoutBuilder; + private boolean offloadToAnotherThread; + private Executor offloadExecutor; + + public BuilderImpl(BuilderEagerDependencies eagerDependencies, Supplier lazyDependencies, + Type valueType) { + this.eagerDependencies = eagerDependencies; + this.lazyDependencies = lazyDependencies; + this.asyncSupport = GuardCommon.asyncSupport(valueType); + + this.description = UUID.randomUUID().toString(); + } + + @Override + public Builder withDescription(String value) { + this.description = Preconditions.checkNotNull(value, "Description must be set"); + return this; + } + + @Override + public BulkheadBuilder withBulkhead() { + return new BulkheadBuilderImpl<>(this); + } + + @Override + public CircuitBreakerBuilder withCircuitBreaker() { + return new CircuitBreakerBuilderImpl<>(this); + } + + @Override + public FallbackBuilder withFallback() { + return new FallbackBuilderImpl<>(this); + } + + @Override + public RateLimitBuilder withRateLimit() { + return new RateLimitBuilderImpl<>(this); + } + + @Override + public RetryBuilder withRetry() { + return new RetryBuilderImpl<>(this); + } + + @Override + public TimeoutBuilder withTimeout() { + return new TimeoutBuilderImpl<>(this); + } + + @Override + public Builder withThreadOffload(boolean value) { + this.offloadToAnotherThread = value; + return this; + } + + @Override + public Builder withThreadOffloadExecutor(Executor executor) { + this.offloadExecutor = Preconditions.checkNotNull(executor, "Thread offload executor must be set"); + return this; + } + + @Override + public TypedGuard build() { + eagerInitialization(); + return new LazyTypedGuard<>(() -> new TypedGuardImpl<>( + buildStrategy(lazyDependencies.get()), asyncSupport, buildEventHandlers())); + } + + // must not access lazyDependencies + final void eagerInitialization() { + if (circuitBreakerBuilder != null && circuitBreakerBuilder.name != null) { + eagerDependencies.cbMaintenance().registerName(circuitBreakerBuilder.name); + } + } + + final EventHandlers buildEventHandlers() { + Consumer cbMaintenanceEventHandler = null; + if (circuitBreakerBuilder != null && circuitBreakerBuilder.name != null) { + cbMaintenanceEventHandler = eagerDependencies.cbMaintenance() + .stateTransitionEventHandler(circuitBreakerBuilder.name); + } + return new EventHandlers( + bulkheadBuilder != null ? bulkheadBuilder.onAccepted : null, + bulkheadBuilder != null ? bulkheadBuilder.onRejected : null, + bulkheadBuilder != null ? bulkheadBuilder.onFinished : null, + cbMaintenanceEventHandler, + circuitBreakerBuilder != null ? circuitBreakerBuilder.onStateChange : null, + circuitBreakerBuilder != null ? circuitBreakerBuilder.onSuccess : null, + circuitBreakerBuilder != null ? circuitBreakerBuilder.onFailure : null, + circuitBreakerBuilder != null ? circuitBreakerBuilder.onPrevented : null, + rateLimitBuilder != null ? rateLimitBuilder.onPermitted : null, + rateLimitBuilder != null ? rateLimitBuilder.onRejected : null, + retryBuilder != null ? retryBuilder.onRetry : null, + retryBuilder != null ? retryBuilder.onSuccess : null, + retryBuilder != null ? retryBuilder.onFailure : null, + timeoutBuilder != null ? timeoutBuilder.onTimeout : null, + timeoutBuilder != null ? timeoutBuilder.onFinished : null); + } + + final FaultToleranceStrategy buildStrategy(BuilderLazyDependencies lazyDependencies) { + FaultToleranceStrategy result = invocation(); + + // thread offload is always enabled + if (offloadToAnotherThread) { + Executor executor = offloadExecutor != null ? offloadExecutor : lazyDependencies.asyncExecutor(); + result = new SyncAsyncSplit<>(new ThreadOffload<>(result, executor), result); + } + + if (lazyDependencies.ftEnabled() && bulkheadBuilder != null) { + result = new Bulkhead<>(result, description, + bulkheadBuilder.limit, + bulkheadBuilder.queueSize, + bulkheadBuilder.syncQueueingEnabled); + } + + if (lazyDependencies.ftEnabled() && timeoutBuilder != null) { + result = new Timeout<>(result, description, timeoutBuilder.durationInMillis, + lazyDependencies.timer()); + } + + if (lazyDependencies.ftEnabled() && rateLimitBuilder != null) { + result = new RateLimit<>(result, description, + rateLimitBuilder.maxInvocations, + rateLimitBuilder.timeWindowInMillis, + rateLimitBuilder.minSpacingInMillis, + rateLimitBuilder.type, + SystemStopwatch.INSTANCE); + } + + if (lazyDependencies.ftEnabled() && circuitBreakerBuilder != null) { + result = new CircuitBreaker<>(result, description, + createExceptionDecision(circuitBreakerBuilder.skipOn, circuitBreakerBuilder.failOn, + circuitBreakerBuilder.whenPredicate), + circuitBreakerBuilder.delayInMillis, + circuitBreakerBuilder.requestVolumeThreshold, + circuitBreakerBuilder.failureRatio, + circuitBreakerBuilder.successThreshold, + SystemStopwatch.INSTANCE, + lazyDependencies.timer()); + + if (circuitBreakerBuilder.name != null) { + CircuitBreaker circuitBreaker = (CircuitBreaker) result; + eagerDependencies.cbMaintenance().register(circuitBreakerBuilder.name, circuitBreaker); + } + } + + if (lazyDependencies.ftEnabled() && retryBuilder != null) { + Supplier backoff = prepareRetryBackoff(retryBuilder); + + result = new Retry<>(result, description, + createResultDecision(retryBuilder.whenResultPredicate), + createExceptionDecision(retryBuilder.abortOn, retryBuilder.retryOn, + retryBuilder.whenExceptionPredicate), + retryBuilder.maxRetries, retryBuilder.maxDurationInMillis, + () -> new ThreadSleepDelay(backoff.get()), + () -> new TimerDelay(backoff.get(), lazyDependencies.timer()), + SystemStopwatch.INSTANCE, + retryBuilder.beforeRetry != null ? ctx -> retryBuilder.beforeRetry.accept(ctx.failure) : null); + } + + // fallback is always enabled + if (fallbackBuilder != null) { + Function> fallbackFunction; + if (asyncSupport != null) { + fallbackFunction = ctx -> { + try { + return asyncSupport.toFuture(ConstantInvoker.of( + fallbackBuilder.handler.apply(ctx.failure))); + } catch (Exception e) { + return Future.ofError(e); + } + }; + } else { + fallbackFunction = ctx -> { + return Future.from(() -> (V) fallbackBuilder.handler.apply(ctx.failure)); + }; + } + + result = new Fallback<>(result, description, fallbackFunction, + createExceptionDecision(fallbackBuilder.skipOn, fallbackBuilder.applyOn, + fallbackBuilder.whenPredicate)); + } + + MetricsProvider metricsProvider = lazyDependencies.metricsProvider(); + if (metricsProvider.isEnabled()) { + MeteredOperation defaultOperation = buildMeteredOperation(); + result = new DelegatingMetricsCollector<>(result, metricsProvider, defaultOperation); + } + + // thread offload is always enabled + if (!offloadToAnotherThread) { + result = new SyncAsyncSplit<>(new RememberEventLoop<>(result, lazyDependencies.eventLoop()), result); + } + + return result; + } + + private MeteredOperation buildMeteredOperation() { + return new BasicMeteredOperationImpl(description, asyncSupport != null, bulkheadBuilder != null, + circuitBreakerBuilder != null, false, rateLimitBuilder != null, + retryBuilder != null, timeoutBuilder != null); + } + + private static ResultDecision createResultDecision(Predicate whenResultPredicate) { + if (whenResultPredicate != null) { + // the builder API accepts a predicate that returns `true` when a result is considered failure, + // but `[CompletionStage]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(Collection> consideredExpected, + Collection> consideredFailure, Predicate 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(Collection> throwableClasses) { + return throwableClasses == null ? SetOfThrowables.EMPTY : SetOfThrowables.create(throwableClasses); + } + + private static Supplier prepareRetryBackoff(RetryBuilderImpl retryBuilder) { + long jitterMs = retryBuilder.jitterInMillis; + Jitter jitter = jitterMs == 0 ? Jitter.ZERO : new RandomJitter(jitterMs); + + if (retryBuilder.exponentialBackoffBuilder != null) { + int factor = retryBuilder.exponentialBackoffBuilder.factor; + long maxDelay = retryBuilder.exponentialBackoffBuilder.maxDelayInMillis; + return () -> new ExponentialBackOff(retryBuilder.delayInMillis, factor, jitter, maxDelay); + } else if (retryBuilder.fibonacciBackoffBuilder != null) { + long maxDelay = retryBuilder.fibonacciBackoffBuilder.maxDelayInMillis; + return () -> new FibonacciBackOff(retryBuilder.delayInMillis, jitter, maxDelay); + } else if (retryBuilder.customBackoffBuilder != null) { + Supplier strategy = retryBuilder.customBackoffBuilder.strategy; + return () -> { + CustomBackoffStrategy instance = strategy.get(); + instance.init(retryBuilder.delayInMillis); + return new CustomBackOff(instance::nextDelayInMillis); + }; + } else { + return () -> new ConstantBackOff(retryBuilder.delayInMillis, jitter); + } + } + + static class BulkheadBuilderImpl implements BulkheadBuilder { + private final BuilderImpl parent; + + private int limit = 10; + private int queueSize = 10; + private boolean syncQueueingEnabled; + + private Runnable onAccepted; + private Runnable onRejected; + private Runnable onFinished; + + BulkheadBuilderImpl(BuilderImpl parent) { + this.parent = parent; + } + + @Override + public BulkheadBuilder limit(int value) { + this.limit = Preconditions.check(value, value >= 1, "Limit must be >= 1"); + return this; + } + + @Override + public BulkheadBuilder queueSize(int value) { + this.queueSize = Preconditions.check(value, value >= 1, "Queue size must be >= 1"); + return this; + } + + @Override + public BulkheadBuilder enableSynchronousQueueing() { + this.syncQueueingEnabled = true; + return this; + } + + @Override + public BulkheadBuilder onAccepted(Runnable callback) { + this.onAccepted = Preconditions.checkNotNull(callback, "Accepted callback must be set"); + return this; + } + + @Override + public BulkheadBuilder onRejected(Runnable callback) { + this.onRejected = Preconditions.checkNotNull(callback, "Rejected callback must be set"); + return this; + } + + @Override + public BulkheadBuilder onFinished(Runnable callback) { + this.onFinished = Preconditions.checkNotNull(callback, "Finished callback must be set"); + return this; + } + + @Override + public Builder done() { + parent.bulkheadBuilder = this; + return parent; + } + } + + static class CircuitBreakerBuilderImpl implements CircuitBreakerBuilder { + private final BuilderImpl parent; + + private Collection> failOn = Collections.singleton(Throwable.class); + private Collection> skipOn = Collections.emptySet(); + private boolean setBasedExceptionDecisionDefined = false; + private Predicate whenPredicate; + private long delayInMillis = 5000; + private int requestVolumeThreshold = 20; + private double failureRatio = 0.5; + private int successThreshold = 1; + + private String name; // unnamed by default + + private Consumer onStateChange; + private Runnable onSuccess; + private Runnable onFailure; + private Runnable onPrevented; + + CircuitBreakerBuilderImpl(BuilderImpl parent) { + this.parent = parent; + } + + @Override + public CircuitBreakerBuilder failOn(Collection> value) { + this.failOn = Preconditions.checkNotNull(value, "Exceptions considered failure must be set"); + this.setBasedExceptionDecisionDefined = true; + return this; + } + + @Override + public CircuitBreakerBuilder skipOn(Collection> value) { + this.skipOn = Preconditions.checkNotNull(value, "Exceptions considered success must be set"); + this.setBasedExceptionDecisionDefined = true; + return this; + } + + @Override + public CircuitBreakerBuilder when(Predicate value) { + this.whenPredicate = Preconditions.checkNotNull(value, "Exception predicate must be set"); + return this; + } + + @Override + public CircuitBreakerBuilder delay(long value, ChronoUnit unit) { + Preconditions.check(value, value >= 0, "Delay must be >= 0"); + Preconditions.checkNotNull(unit, "Delay unit must be set"); + + this.delayInMillis = timeInMillis(value, unit); + return this; + } + + @Override + public CircuitBreakerBuilder requestVolumeThreshold(int value) { + this.requestVolumeThreshold = Preconditions.check(value, value >= 1, "Request volume threshold must be >= 1"); + return this; + } + + @Override + public CircuitBreakerBuilder failureRatio(double value) { + this.failureRatio = Preconditions.check(value, value >= 0 && value <= 1, "Failure ratio must be >= 0 and <= 1"); + return this; + } + + @Override + public CircuitBreakerBuilder successThreshold(int value) { + this.successThreshold = Preconditions.check(value, value >= 1, "Success threshold must be >= 1"); + return this; + } + + @Override + public CircuitBreakerBuilder name(String value) { + this.name = Preconditions.checkNotNull(value, "Circuit breaker name must be set"); + return this; + } + + @Override + public CircuitBreakerBuilder onStateChange(Consumer callback) { + this.onStateChange = Preconditions.checkNotNull(callback, "On state change callback must be set"); + return this; + } + + @Override + public CircuitBreakerBuilder onSuccess(Runnable callback) { + this.onSuccess = Preconditions.checkNotNull(callback, "On success callback must be set"); + return this; + } + + @Override + public CircuitBreakerBuilder onFailure(Runnable callback) { + this.onFailure = Preconditions.checkNotNull(callback, "On failure callback must be set"); + return this; + } + + @Override + public CircuitBreakerBuilder onPrevented(Runnable callback) { + this.onPrevented = Preconditions.checkNotNull(callback, "On prevented callback must be set"); + return this; + } + + @Override + public Builder done() { + if (whenPredicate != null && setBasedExceptionDecisionDefined) { + throw new IllegalStateException("The when() method may not be combined with failOn() / skipOn()"); + } + + parent.circuitBreakerBuilder = this; + return parent; + } + } + + static class FallbackBuilderImpl implements FallbackBuilder { + private final BuilderImpl parent; + + private Function handler; + private Collection> applyOn = Collections.singleton(Throwable.class); + private Collection> skipOn = Collections.emptySet(); + private boolean setBasedExceptionDecisionDefined = false; + private Predicate whenPredicate; + + FallbackBuilderImpl(BuilderImpl parent) { + this.parent = parent; + } + + @Override + public FallbackBuilder handler(Supplier value) { + Preconditions.checkNotNull(value, "Fallback handler must be set"); + this.handler = ignored -> value.get(); + return this; + } + + @Override + public FallbackBuilder handler(Function value) { + this.handler = Preconditions.checkNotNull(value, "Fallback handler must be set"); + return this; + } + + @Override + public FallbackBuilder applyOn(Collection> value) { + this.applyOn = Preconditions.checkNotNull(value, "Exceptions to apply fallback on must be set"); + this.setBasedExceptionDecisionDefined = true; + return this; + } + + @Override + public FallbackBuilder skipOn(Collection> value) { + this.skipOn = Preconditions.checkNotNull(value, "Exceptions to skip fallback on must be set"); + this.setBasedExceptionDecisionDefined = true; + return this; + } + + @Override + public FallbackBuilder when(Predicate value) { + this.whenPredicate = Preconditions.checkNotNull(value, "Exception predicate must be set"); + return this; + } + + @Override + public Builder done() { + Preconditions.checkNotNull(handler, "Fallback handler must be set"); + + if (whenPredicate != null && setBasedExceptionDecisionDefined) { + throw new IllegalStateException("The when() method may not be combined with applyOn() / skipOn()"); + } + + parent.fallbackBuilder = this; + return parent; + } + } + + static class RateLimitBuilderImpl implements RateLimitBuilder { + private final BuilderImpl parent; + + private int maxInvocations = 100; + private long timeWindowInMillis = 1000; + private long minSpacingInMillis = 0; + private RateLimitType type = RateLimitType.FIXED; + + private Runnable onPermitted; + private Runnable onRejected; + + RateLimitBuilderImpl(BuilderImpl parent) { + this.parent = parent; + } + + @Override + public RateLimitBuilder limit(int value) { + this.maxInvocations = Preconditions.check(value, value >= 1, "Rate limit must be >= 1"); + return this; + } + + @Override + public RateLimitBuilder window(long value, ChronoUnit unit) { + Preconditions.check(value, value >= 1, "Time window length must be >= 1"); + Preconditions.checkNotNull(unit, "Time window length unit must be set"); + + this.timeWindowInMillis = timeInMillis(value, unit); + return this; + } + + @Override + public RateLimitBuilder minSpacing(long value, ChronoUnit unit) { + Preconditions.check(value, value >= 0, "Min spacing must be >= 0"); + Preconditions.checkNotNull(unit, "Min spacing unit must be set"); + + this.minSpacingInMillis = timeInMillis(value, unit); + return this; + } + + @Override + public RateLimitBuilder type(RateLimitType value) { + this.type = Preconditions.checkNotNull(value, "Time window type must be set"); + return this; + } + + @Override + public RateLimitBuilder onPermitted(Runnable callback) { + this.onPermitted = Preconditions.checkNotNull(callback, "Permitted callback must be set"); + return this; + } + + @Override + public RateLimitBuilder onRejected(Runnable callback) { + this.onRejected = Preconditions.checkNotNull(callback, "Rejected callback must be set"); + return this; + } + + @Override + public Builder done() { + parent.rateLimitBuilder = this; + return parent; + } + } + + static class RetryBuilderImpl implements RetryBuilder { + private final BuilderImpl parent; + + private int maxRetries = 3; + private long delayInMillis = 0; + private long maxDurationInMillis = 180_000; + private long jitterInMillis = 200; + private Collection> retryOn = Collections.singleton(Exception.class); + private Collection> abortOn = Collections.emptySet(); + private boolean setBasedExceptionDecisionDefined = false; + private Predicate whenExceptionPredicate; + private Predicate whenResultPredicate; + private Consumer beforeRetry; + + private ExponentialBackoffBuilderImpl exponentialBackoffBuilder; + private FibonacciBackoffBuilderImpl fibonacciBackoffBuilder; + private CustomBackoffBuilderImpl customBackoffBuilder; + + private Runnable onRetry; + private Runnable onSuccess; + private Runnable onFailure; + + RetryBuilderImpl(BuilderImpl parent) { + this.parent = parent; + } + + @Override + public RetryBuilder maxRetries(int value) { + this.maxRetries = Preconditions.check(value, value >= -1, "Max retries must be >= -1"); + return this; + } + + @Override + public RetryBuilder delay(long value, ChronoUnit unit) { + Preconditions.check(value, value >= 0, "Delay must be >= 0"); + Preconditions.checkNotNull(unit, "Delay unit must be set"); + + this.delayInMillis = timeInMillis(value, unit); + return this; + } + + @Override + public RetryBuilder maxDuration(long value, ChronoUnit unit) { + Preconditions.check(value, value >= 0, "Max duration must be >= 0"); + Preconditions.checkNotNull(unit, "Max duration unit must be set"); + + this.maxDurationInMillis = timeInMillis(value, unit); + return this; + } + + @Override + public RetryBuilder jitter(long value, ChronoUnit unit) { + Preconditions.check(value, value >= 0, "Jitter must be >= 0"); + Preconditions.checkNotNull(unit, "Jitter unit must be set"); + + this.jitterInMillis = timeInMillis(value, unit); + return this; + } + + @Override + public RetryBuilder retryOn(Collection> value) { + this.retryOn = Preconditions.checkNotNull(value, "Exceptions to retry on must be set"); + this.setBasedExceptionDecisionDefined = true; + return this; + } + + @Override + public RetryBuilder abortOn(Collection> value) { + this.abortOn = Preconditions.checkNotNull(value, "Exceptions to abort retrying on must be set"); + this.setBasedExceptionDecisionDefined = true; + return this; + } + + @Override + public RetryBuilder whenResult(Predicate value) { + this.whenResultPredicate = Preconditions.checkNotNull(value, "Result predicate must be set"); + return this; + } + + @Override + public RetryBuilder whenException(Predicate value) { + this.whenExceptionPredicate = Preconditions.checkNotNull(value, "Exception predicate must be set"); + return this; + } + + @Override + public RetryBuilder beforeRetry(Runnable value) { + Preconditions.checkNotNull(value, "Before retry handler must be set"); + this.beforeRetry = ignored -> value.run(); + return this; + } + + @Override + public RetryBuilder beforeRetry(Consumer value) { + this.beforeRetry = Preconditions.checkNotNull(value, "Before retry handler must be set"); + return this; + } + + @Override + public ExponentialBackoffBuilder withExponentialBackoff() { + return new ExponentialBackoffBuilderImpl<>(this); + } + + @Override + public FibonacciBackoffBuilder withFibonacciBackoff() { + return new FibonacciBackoffBuilderImpl<>(this); + } + + @Override + public CustomBackoffBuilder withCustomBackoff() { + return new CustomBackoffBuilderImpl<>(this); + } + + @Override + public RetryBuilder onRetry(Runnable callback) { + this.onRetry = Preconditions.checkNotNull(callback, "Retry callback must be set"); + return this; + } + + @Override + public RetryBuilder onSuccess(Runnable callback) { + this.onSuccess = Preconditions.checkNotNull(callback, "Success callback must be set"); + return this; + } + + @Override + public RetryBuilder onFailure(Runnable callback) { + this.onFailure = Preconditions.checkNotNull(callback, "Failure callback must be set"); + return this; + } + + @Override + public Builder done() { + if (whenExceptionPredicate != null && setBasedExceptionDecisionDefined) { + throw new IllegalStateException("The whenException() method may not be combined with retryOn()/abortOn()"); + } + + int backoffStrategies = 0; + if (exponentialBackoffBuilder != null) { + backoffStrategies++; + } + if (fibonacciBackoffBuilder != null) { + backoffStrategies++; + } + if (customBackoffBuilder != null) { + backoffStrategies++; + } + if (backoffStrategies > 1) { + throw new IllegalStateException("Only one backoff strategy may be set for retry"); + } + + parent.retryBuilder = this; + return parent; + } + + static class ExponentialBackoffBuilderImpl implements ExponentialBackoffBuilder { + private final RetryBuilderImpl parent; + + private int factor = 2; + private long maxDelayInMillis = 60_000; + + ExponentialBackoffBuilderImpl(RetryBuilderImpl parent) { + this.parent = parent; + } + + @Override + public ExponentialBackoffBuilder factor(int value) { + this.factor = Preconditions.check(value, value >= 1, "Factor must be >= 1"); + return this; + } + + @Override + public ExponentialBackoffBuilder maxDelay(long value, ChronoUnit unit) { + Preconditions.check(value, value >= 0, "Max delay must be >= 0"); + Preconditions.checkNotNull(unit, "Max delay unit must be set"); + + this.maxDelayInMillis = timeInMillis(value, unit); + return this; + } + + @Override + public RetryBuilder done() { + parent.exponentialBackoffBuilder = this; + return parent; + } + } + + static class FibonacciBackoffBuilderImpl implements FibonacciBackoffBuilder { + private final RetryBuilderImpl parent; + + private long maxDelayInMillis = 60_000; + + FibonacciBackoffBuilderImpl(RetryBuilderImpl parent) { + this.parent = parent; + } + + @Override + public FibonacciBackoffBuilder maxDelay(long value, ChronoUnit unit) { + Preconditions.check(value, value >= 0, "Max delay must be >= 0"); + Preconditions.checkNotNull(unit, "Max delay unit must be set"); + + this.maxDelayInMillis = timeInMillis(value, unit); + return this; + } + + @Override + public RetryBuilder done() { + parent.fibonacciBackoffBuilder = this; + return parent; + } + } + + static class CustomBackoffBuilderImpl implements CustomBackoffBuilder { + private final RetryBuilderImpl parent; + + private Supplier strategy; + + CustomBackoffBuilderImpl(RetryBuilderImpl parent) { + this.parent = parent; + } + + @Override + public CustomBackoffBuilder strategy(Supplier value) { + this.strategy = Preconditions.checkNotNull(value, "Custom backoff strategy must be set"); + return this; + } + + @Override + public RetryBuilder done() { + Preconditions.checkNotNull(strategy, "Custom backoff strategy must be set"); + + parent.customBackoffBuilder = this; + return parent; + } + } + } + + static class TimeoutBuilderImpl implements TimeoutBuilder { + private final BuilderImpl parent; + + private long durationInMillis = 1000; + + private Runnable onTimeout; + private Runnable onFinished; + + TimeoutBuilderImpl(BuilderImpl parent) { + this.parent = parent; + } + + @Override + public TimeoutBuilder duration(long value, ChronoUnit unit) { + Preconditions.check(value, value >= 0, "Timeout duration must be >= 0"); + Preconditions.checkNotNull(unit, "Timeout duration unit must be set"); + + this.durationInMillis = timeInMillis(value, unit); + return this; + } + + @Override + public TimeoutBuilder onTimeout(Runnable callback) { + this.onTimeout = Preconditions.checkNotNull(callback, "Timeout callback must be set"); + return this; + } + + @Override + public TimeoutBuilder onFinished(Runnable callback) { + this.onFinished = Preconditions.checkNotNull(callback, "Finished callback must be set"); + return this; + } + + @Override + public Builder done() { + parent.timeoutBuilder = this; + return parent; + } + } + } +} diff --git a/implementation/core/src/main/java/io/smallrye/faulttolerance/core/async/SyncAsyncSplit.java b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/async/SyncAsyncSplit.java new file mode 100644 index 00000000..1b9084ec --- /dev/null +++ b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/async/SyncAsyncSplit.java @@ -0,0 +1,26 @@ +package io.smallrye.faulttolerance.core.async; + +import static io.smallrye.faulttolerance.core.util.Preconditions.checkNotNull; + +import io.smallrye.faulttolerance.core.FaultToleranceContext; +import io.smallrye.faulttolerance.core.FaultToleranceStrategy; +import io.smallrye.faulttolerance.core.Future; + +public class SyncAsyncSplit implements FaultToleranceStrategy { + private final FaultToleranceStrategy asyncDelegate; + private final FaultToleranceStrategy syncDelegate; + + public SyncAsyncSplit(FaultToleranceStrategy asyncDelegate, FaultToleranceStrategy syncDelegate) { + this.asyncDelegate = checkNotNull(asyncDelegate, "Async delegate must be set"); + this.syncDelegate = checkNotNull(syncDelegate, "Sync delegate must be set"); + } + + @Override + public Future apply(FaultToleranceContext ctx) { + if (ctx.isAsync()) { + return asyncDelegate.apply(ctx); + } else { + return syncDelegate.apply(ctx); + } + } +} diff --git a/implementation/core/src/main/java/io/smallrye/faulttolerance/core/metrics/DelegatingMeteredOperation.java b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/metrics/DelegatingMeteredOperation.java index 779090cb..7e28eed8 100644 --- a/implementation/core/src/main/java/io/smallrye/faulttolerance/core/metrics/DelegatingMeteredOperation.java +++ b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/metrics/DelegatingMeteredOperation.java @@ -10,8 +10,8 @@ final class DelegatingMeteredOperation implements MeteredOperation { } @Override - public boolean isAsynchronous() { - return operation.isAsynchronous(); + public boolean mayBeAsynchronous() { + return operation.mayBeAsynchronous(); } @Override diff --git a/implementation/core/src/main/java/io/smallrye/faulttolerance/core/metrics/MeteredOperation.java b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/metrics/MeteredOperation.java index 77061576..16f6c903 100644 --- a/implementation/core/src/main/java/io/smallrye/faulttolerance/core/metrics/MeteredOperation.java +++ b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/metrics/MeteredOperation.java @@ -1,7 +1,7 @@ package io.smallrye.faulttolerance.core.metrics; public interface MeteredOperation { - boolean isAsynchronous(); + boolean mayBeAsynchronous(); boolean hasBulkhead(); diff --git a/implementation/core/src/main/java/io/smallrye/faulttolerance/core/metrics/MetricsCollector.java b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/metrics/MetricsCollector.java index 4bcaff1e..3626b4e4 100644 --- a/implementation/core/src/main/java/io/smallrye/faulttolerance/core/metrics/MetricsCollector.java +++ b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/metrics/MetricsCollector.java @@ -20,7 +20,7 @@ public class MetricsCollector implements FaultToleranceStrategy { private final FaultToleranceStrategy delegate; private final MetricsRecorder metrics; - private final boolean isAsync; + private final boolean mayBeAsync; private final boolean hasBulkhead; private final boolean hasCircuitBreaker; private final boolean hasRateLimit; @@ -48,7 +48,7 @@ public class MetricsCollector implements FaultToleranceStrategy { public MetricsCollector(FaultToleranceStrategy delegate, MetricsRecorder metrics, MeteredOperation operation) { this.delegate = delegate; this.metrics = metrics; - this.isAsync = operation.isAsynchronous(); + this.mayBeAsync = operation.mayBeAsynchronous(); this.hasBulkhead = operation.hasBulkhead(); this.hasCircuitBreaker = operation.hasCircuitBreaker(); this.hasRateLimit = operation.hasRateLimit(); @@ -73,7 +73,7 @@ public MetricsCollector(FaultToleranceStrategy delegate, MetricsRecorder metr if (hasBulkhead) { metrics.registerBulkheadExecutionsRunning(runningExecutions::get); - if (isAsync) { + if (mayBeAsync) { metrics.registerBulkheadExecutionsWaiting(waitingExecutions::get); } } @@ -204,7 +204,7 @@ private void registerMetrics(FaultToleranceContext ctx) { metrics.updateBulkheadRunningDuration(System.nanoTime() - runningStart.get()); }); - if (isAsync) { + if (mayBeAsync) { AtomicLong waitingStart = new AtomicLong(); ctx.registerEventHandler(BulkheadEvents.StartedWaiting.class, ignored -> { waitingExecutions.incrementAndGet(); diff --git a/implementation/core/src/main/java/io/smallrye/faulttolerance/core/metrics/MicroProfileMetricsRecorder.java b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/metrics/MicroProfileMetricsRecorder.java index a134fe02..ab91b27f 100644 --- a/implementation/core/src/main/java/io/smallrye/faulttolerance/core/metrics/MicroProfileMetricsRecorder.java +++ b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/metrics/MicroProfileMetricsRecorder.java @@ -127,7 +127,7 @@ private void registerMetrics(MeteredOperation operation) { registry.counter(BULKHEAD_CALLS_TOTAL, methodTag, BULKHEAD_RESULT_REJECTED).getCount(); registry.histogram(BULKHEAD_RUNNING_DURATION_METADATA, methodTag).getCount(); - if (operation.isAsynchronous()) { + if (operation.mayBeAsynchronous()) { registry.histogram(BULKHEAD_WAITING_DURATION_METADATA, methodTag).getCount(); } } diff --git a/implementation/core/src/main/java/io/smallrye/faulttolerance/core/metrics/MicrometerRecorder.java b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/metrics/MicrometerRecorder.java index 8761a28e..02f3ac23 100644 --- a/implementation/core/src/main/java/io/smallrye/faulttolerance/core/metrics/MicrometerRecorder.java +++ b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/metrics/MicrometerRecorder.java @@ -117,7 +117,7 @@ private void registerMetrics(MeteredOperation operation) { registry.counter(BULKHEAD_CALLS_TOTAL, Arrays.asList(methodTag, BULKHEAD_RESULT_REJECTED)); registry.timer(BULKHEAD_RUNNING_DURATION, methodTagSingleton); - if (operation.isAsynchronous()) { + if (operation.mayBeAsynchronous()) { registry.timer(BULKHEAD_WAITING_DURATION, methodTagSingleton); } } diff --git a/implementation/core/src/main/java/io/smallrye/faulttolerance/core/metrics/OpenTelemetryRecorder.java b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/metrics/OpenTelemetryRecorder.java index 0e09ad84..d18fb5e3 100644 --- a/implementation/core/src/main/java/io/smallrye/faulttolerance/core/metrics/OpenTelemetryRecorder.java +++ b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/metrics/OpenTelemetryRecorder.java @@ -134,7 +134,7 @@ public OpenTelemetryRecorder(Meter meter, MeteredOperation operation) { .setUnit("seconds") .setExplicitBucketBoundariesAdvice(BUCKET_BOUNDARIES) .build(); - if (operation.isAsynchronous()) { + if (operation.mayBeAsynchronous()) { this.bulkheadWaitingDuration = meter.histogramBuilder(BULKHEAD_WAITING_DURATION) .setUnit("seconds") .setExplicitBucketBoundariesAdvice(BUCKET_BOUNDARIES) diff --git a/implementation/core/src/main/java/io/smallrye/faulttolerance/core/util/DirectExecutor.java b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/util/DirectExecutor.java deleted file mode 100644 index c6571bbb..00000000 --- a/implementation/core/src/main/java/io/smallrye/faulttolerance/core/util/DirectExecutor.java +++ /dev/null @@ -1,19 +0,0 @@ -package io.smallrye.faulttolerance.core.util; - -import java.util.concurrent.Executor; - -/** - * An {@link Executor} that runs tasks directly on the caller thread. - * Any exception thrown by the task is propagated back to the caller. - */ -public final class DirectExecutor implements Executor { - public static final DirectExecutor INSTANCE = new DirectExecutor(); - - private DirectExecutor() { - } - - @Override - public void execute(Runnable runnable) { - runnable.run(); - } -} diff --git a/implementation/core/src/test/java/io/smallrye/faulttolerance/core/metrics/GeneralMetricsTest.java b/implementation/core/src/test/java/io/smallrye/faulttolerance/core/metrics/GeneralMetricsTest.java index 2b85ab4d..be16a8c9 100644 --- a/implementation/core/src/test/java/io/smallrye/faulttolerance/core/metrics/GeneralMetricsTest.java +++ b/implementation/core/src/test/java/io/smallrye/faulttolerance/core/metrics/GeneralMetricsTest.java @@ -41,7 +41,7 @@ public void failingInvocation() { private static class MockMeteredOperation implements MeteredOperation { @Override - public boolean isAsynchronous() { + public boolean mayBeAsynchronous() { return false; } diff --git a/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/CdiLogger.java b/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/CdiLogger.java index 70e184f2..da2ab4c7 100644 --- a/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/CdiLogger.java +++ b/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/CdiLogger.java @@ -47,4 +47,7 @@ interface CdiLogger extends BasicLogger { DefinitionException beforeRetryAnnotationWithoutRetry(MethodDescriptor method); DefinitionException beforeRetryAnnotationWithoutRetry(Class clazz); + + @Message(id = 8, value = "Multiple Guard/TypedGuard beans have the same identifier '%s': %s") + DefinitionException multipleGuardsWithTheSameIdentifier(String identifier, Set beans); } diff --git a/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/CdiFaultToleranceSpi.java b/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/CdiSpi.java similarity index 83% rename from implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/CdiFaultToleranceSpi.java rename to implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/CdiSpi.java index 21854cb7..fd91e4e6 100644 --- a/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/CdiFaultToleranceSpi.java +++ b/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/CdiSpi.java @@ -1,5 +1,6 @@ package io.smallrye.faulttolerance; +import java.lang.reflect.Type; import java.util.concurrent.ExecutorService; import java.util.function.Function; @@ -11,16 +12,20 @@ import io.smallrye.faulttolerance.api.CircuitBreakerMaintenance; import io.smallrye.faulttolerance.api.FaultTolerance; -import io.smallrye.faulttolerance.api.FaultToleranceSpi; +import io.smallrye.faulttolerance.api.Guard; +import io.smallrye.faulttolerance.api.Spi; +import io.smallrye.faulttolerance.api.TypedGuard; import io.smallrye.faulttolerance.core.apiimpl.BasicCircuitBreakerMaintenanceImpl; import io.smallrye.faulttolerance.core.apiimpl.BuilderEagerDependencies; import io.smallrye.faulttolerance.core.apiimpl.BuilderLazyDependencies; import io.smallrye.faulttolerance.core.apiimpl.FaultToleranceImpl; +import io.smallrye.faulttolerance.core.apiimpl.GuardImpl; +import io.smallrye.faulttolerance.core.apiimpl.TypedGuardImpl; import io.smallrye.faulttolerance.core.event.loop.EventLoop; import io.smallrye.faulttolerance.core.metrics.MetricsProvider; import io.smallrye.faulttolerance.core.timer.Timer; -public class CdiFaultToleranceSpi implements FaultToleranceSpi { +public class CdiSpi implements Spi { @Singleton public static class EagerDependencies implements BuilderEagerDependencies { @Inject @@ -41,9 +46,6 @@ public static class LazyDependencies implements BuilderLazyDependencies { @Inject ExecutorHolder executorHolder; - @Inject - CircuitBreakerMaintenanceImpl cbMaintenance; - @Inject MetricsProvider metricsProvider; @@ -101,11 +103,23 @@ public int priority() { } @Override + public Guard.Builder newGuardBuilder() { + return new GuardImpl.BuilderImpl(eagerDependencies(), this::lazyDependencies); + } + + @Override + public TypedGuard.Builder newTypedGuardBuilder(Type type) { + return new TypedGuardImpl.BuilderImpl<>(eagerDependencies(), this::lazyDependencies, type); + } + + @Override + @Deprecated(forRemoval = true) public FaultTolerance.Builder newBuilder(Function, R> finisher) { return new FaultToleranceImpl.BuilderImpl<>(eagerDependencies(), this::lazyDependencies, null, finisher); } @Override + @Deprecated(forRemoval = true) public FaultTolerance.Builder newAsyncBuilder(Class asyncType, Function, R> finisher) { return new FaultToleranceImpl.BuilderImpl<>(eagerDependencies(), this::lazyDependencies, asyncType, finisher); } diff --git a/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/FaultToleranceExtension.java b/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/FaultToleranceExtension.java index 809fb7a9..bb9a4793 100644 --- a/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/FaultToleranceExtension.java +++ b/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/FaultToleranceExtension.java @@ -21,6 +21,7 @@ import java.io.InputStream; import java.lang.annotation.Annotation; import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.security.AccessController; import java.security.PrivilegedAction; @@ -42,10 +43,12 @@ import jakarta.enterprise.inject.spi.AnnotatedField; import jakarta.enterprise.inject.spi.AnnotatedMethod; import jakarta.enterprise.inject.spi.AnnotatedType; +import jakarta.enterprise.inject.spi.Bean; import jakarta.enterprise.inject.spi.BeanManager; import jakarta.enterprise.inject.spi.BeforeBeanDiscovery; import jakarta.enterprise.inject.spi.Extension; import jakarta.enterprise.inject.spi.ProcessAnnotatedType; +import jakarta.enterprise.inject.spi.ProcessBean; import jakarta.enterprise.inject.spi.ProcessManagedBean; import jakarta.enterprise.util.AnnotationLiteral; @@ -58,15 +61,19 @@ import org.eclipse.microprofile.faulttolerance.Timeout; import io.smallrye.common.annotation.Blocking; +import io.smallrye.common.annotation.Identifier; import io.smallrye.common.annotation.NonBlocking; import io.smallrye.faulttolerance.api.ApplyFaultTolerance; +import io.smallrye.faulttolerance.api.ApplyGuard; import io.smallrye.faulttolerance.api.AsynchronousNonBlocking; import io.smallrye.faulttolerance.api.BeforeRetry; import io.smallrye.faulttolerance.api.CustomBackoff; import io.smallrye.faulttolerance.api.ExponentialBackoff; import io.smallrye.faulttolerance.api.FibonacciBackoff; +import io.smallrye.faulttolerance.api.Guard; import io.smallrye.faulttolerance.api.RateLimit; import io.smallrye.faulttolerance.api.RetryWhen; +import io.smallrye.faulttolerance.api.TypedGuard; import io.smallrye.faulttolerance.autoconfig.FaultToleranceMethod; import io.smallrye.faulttolerance.config.FaultToleranceMethods; import io.smallrye.faulttolerance.config.FaultToleranceOperation; @@ -92,6 +99,8 @@ public class FaultToleranceExtension implements Extension { private final ConcurrentMap> existingCircuitBreakerNames = new ConcurrentHashMap<>(); + private final ConcurrentMap> existingGuards = new ConcurrentHashMap<>(); + private final Set metricsIntegrations; private static boolean isPresent(String className) { @@ -140,6 +149,7 @@ void registerInterceptorBindings(@Observes BeforeBeanDiscovery bbd, BeanManager // do _not_ trigger the fault tolerance interceptor alone, only in combination // with other fault tolerance annotations bbd.addInterceptorBinding(new FTInterceptorBindingAnnotatedType<>(bm.createAnnotatedType(ApplyFaultTolerance.class))); + bbd.addInterceptorBinding(new FTInterceptorBindingAnnotatedType<>(bm.createAnnotatedType(ApplyGuard.class))); bbd.addInterceptorBinding(new FTInterceptorBindingAnnotatedType<>(bm.createAnnotatedType(Asynchronous.class))); bbd.addInterceptorBinding(new FTInterceptorBindingAnnotatedType<>( bm.createAnnotatedType(AsynchronousNonBlocking.class))); @@ -170,10 +180,10 @@ void registerInterceptorBindings(@Observes BeforeBeanDiscovery bbd, BeanManager bbd.addAnnotatedType(bm.createAnnotatedType(RequestContextIntegration.class), RequestContextIntegration.class.getName()); bbd.addAnnotatedType(bm.createAnnotatedType(SpecCompatibility.class), SpecCompatibility.class.getName()); - bbd.addAnnotatedType(bm.createAnnotatedType(CdiFaultToleranceSpi.EagerDependencies.class), - CdiFaultToleranceSpi.EagerDependencies.class.getName()); - bbd.addAnnotatedType(bm.createAnnotatedType(CdiFaultToleranceSpi.LazyDependencies.class), - CdiFaultToleranceSpi.LazyDependencies.class.getName()); + bbd.addAnnotatedType(bm.createAnnotatedType(CdiSpi.EagerDependencies.class), + CdiSpi.EagerDependencies.class.getName()); + bbd.addAnnotatedType(bm.createAnnotatedType(CdiSpi.LazyDependencies.class), + CdiSpi.LazyDependencies.class.getName()); if (metricsIntegrations.size() > 1) { bbd.addAnnotatedType(bm.createAnnotatedType(CompoundMetricsProvider.class), @@ -294,6 +304,23 @@ void collectFaultToleranceOperations(@Observes ProcessManagedBean event) { } } + void processBean(@Observes ProcessBean pb) { + Bean bean = pb.getBean(); + boolean isGuard = bean.getTypes().contains(Guard.class) + || bean.getTypes().contains(TypedGuard.class) + || bean.getTypes().stream().anyMatch(it -> it instanceof ParameterizedType + && ((ParameterizedType) it).getRawType().equals(TypedGuard.class)); + if (isGuard) { + for (Annotation ann : bean.getQualifiers()) { + if (ann instanceof Identifier) { + existingGuards + .computeIfAbsent(((Identifier) ann).value(), ignored -> new HashSet<>()) + .add(bean.toString()); + } + } + } + } + void validate(@Observes AfterDeploymentValidation event) { for (Map.Entry> entry : existingCircuitBreakerNames.entrySet()) { if (entry.getValue().size() > 1) { @@ -301,6 +328,16 @@ void validate(@Observes AfterDeploymentValidation event) { entry.getKey(), entry.getValue())); } } + // don't clear the `existingCircuitBreakerNames`, they're used later + // by `CircuitBreakerMaintenance` (see `getExistingCircuitBreakerNames()`) + + for (Map.Entry> entry : existingGuards.entrySet()) { + if (entry.getValue().size() > 1) { + event.addDeploymentProblem(LOG.multipleGuardsWithTheSameIdentifier( + entry.getKey(), entry.getValue())); + } + } + existingGuards.clear(); } private static String getCacheKey(Class beanClass, Method method) { diff --git a/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/FaultToleranceInterceptor.java b/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/FaultToleranceInterceptor.java index 6d61ad03..47efa968 100644 --- a/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/FaultToleranceInterceptor.java +++ b/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/FaultToleranceInterceptor.java @@ -51,13 +51,19 @@ import io.smallrye.faulttolerance.api.BeforeRetryHandler; import io.smallrye.faulttolerance.api.CustomBackoffStrategy; import io.smallrye.faulttolerance.api.FaultTolerance; +import io.smallrye.faulttolerance.api.Guard; import io.smallrye.faulttolerance.api.NeverOnResult; +import io.smallrye.faulttolerance.api.TypedGuard; import io.smallrye.faulttolerance.config.FaultToleranceOperation; import io.smallrye.faulttolerance.core.FailureContext; import io.smallrye.faulttolerance.core.FaultToleranceContext; import io.smallrye.faulttolerance.core.FaultToleranceStrategy; import io.smallrye.faulttolerance.core.Future; +import io.smallrye.faulttolerance.core.apiimpl.GuardImpl; import io.smallrye.faulttolerance.core.apiimpl.LazyFaultTolerance; +import io.smallrye.faulttolerance.core.apiimpl.LazyGuard; +import io.smallrye.faulttolerance.core.apiimpl.LazyTypedGuard; +import io.smallrye.faulttolerance.core.apiimpl.TypedGuardImpl; import io.smallrye.faulttolerance.core.async.FutureExecution; import io.smallrye.faulttolerance.core.async.RememberEventLoop; import io.smallrye.faulttolerance.core.async.ThreadOffload; @@ -143,6 +149,10 @@ public class FaultToleranceInterceptor { private final Instance> configuredFaultTolerance; + private final Instance configuredGuard; + + private final Instance> configuredTypedGuard; + @Inject public FaultToleranceInterceptor( @Intercepted Bean interceptedBean, @@ -155,7 +165,9 @@ public FaultToleranceInterceptor( RequestContextIntegration requestContextIntegration, CircuitBreakerMaintenanceImpl cbMaintenance, SpecCompatibility specCompatibility, - @Any Instance> configuredFaultTolerance) { + @Any Instance> configuredFaultTolerance, + @Any Instance configuredGuard, + @Any Instance> configuredTypedGuard) { this.interceptedBean = interceptedBean; this.operationProvider = operationProvider; this.cache = cache; @@ -169,6 +181,8 @@ public FaultToleranceInterceptor( this.cbMaintenance = cbMaintenance; this.specCompatibility = specCompatibility; this.configuredFaultTolerance = configuredFaultTolerance; + this.configuredGuard = configuredGuard; + this.configuredTypedGuard = configuredTypedGuard; } @AroundInvoke @@ -179,7 +193,9 @@ public Object intercept(InvocationContext interceptionContext) throws Throwable FaultToleranceOperation operation = operationProvider.get(beanClass, method); if (operation.hasApplyFaultTolerance()) { - return preconfiguredFlow(operation, interceptionContext); + return applyFaultToleranceFlow(operation, interceptionContext); + } else if (operation.hasApplyGuard()) { + return applyGuardFlow(operation, interceptionContext); } else if (specCompatibility.isOperationTrulyAsynchronous(operation)) { return asyncFlow(operation, interceptionContext, point); } else if (specCompatibility.isOperationPseudoAsynchronous(operation)) { @@ -189,7 +205,12 @@ public Object intercept(InvocationContext interceptionContext) throws Throwable } } - private Object preconfiguredFlow(FaultToleranceOperation operation, InvocationContext interceptionContext) + // V = value type, e.g. String + // T = result type, e.g. String or CompletionStage or Uni + // + // in synchronous scenario, V = T + // in asynchronous scenario, T is an async type that eventually produces V + private T applyFaultToleranceFlow(FaultToleranceOperation operation, InvocationContext interceptionContext) throws Exception { String identifier = operation.getApplyFaultTolerance().value(); Instance> instance = configuredFaultTolerance.select(Identifier.Literal.of(identifier)); @@ -211,7 +232,7 @@ private Object preconfiguredFlow(FaultToleranceOperation operation, InvocationCo AsyncSupport fromConfigured = asyncType == null ? null : AsyncSupportRegistry.get(new Class[0], asyncType); if (forOperation == null && fromConfigured == null) { - return lazyFaultTolerance.call(interceptionContext::proceed, meteredOperationName); + return (T) lazyFaultTolerance.call(interceptionContext::proceed, meteredOperationName); } else if (forOperation == null) { throw new FaultToleranceException("Configured fault tolerance '" + identifier + "' expects the operation to " + fromConfigured.mustDescription() @@ -225,18 +246,61 @@ private Object preconfiguredFlow(FaultToleranceOperation operation, InvocationCo + "' expects the operation to " + fromConfigured.mustDescription() + ", but it " + forOperation.doesDescription() + ": " + operation); } else { - return lazyFaultTolerance.call(interceptionContext::proceed, meteredOperationName); + return (T) lazyFaultTolerance.call(interceptionContext::proceed, meteredOperationName); + } + } + + // V = value type, e.g. String + // T = result type, e.g. String or CompletionStage or Uni + // + // in synchronous scenario, V = T + // in asynchronous scenario, T is an async type that eventually produces V + private T applyGuardFlow(FaultToleranceOperation operation, InvocationContext interceptionContext) throws Exception { + String identifier = operation.getApplyGuard().value(); + Instance guardInstance = configuredGuard.select(Identifier.Literal.of(identifier)); + Instance> typedGuardInstance = (Instance) configuredTypedGuard.select(Identifier.Literal.of(identifier)); + if (!guardInstance.isResolvable() && !typedGuardInstance.isResolvable()) { + throw new FaultToleranceException("Can't resolve a bean of type " + Guard.class.getName() + + " or " + TypedGuard.class.getName() + + " with qualifier @" + Identifier.class.getName() + "(\"" + identifier + "\")"); + } + + MeteredOperationName meteredOperationName = new MeteredOperationName(operation.getName()); + + if (guardInstance.isResolvable()) { + Guard guard = guardInstance.get(); + if (!(guard instanceof LazyGuard)) { + throw new FaultToleranceException("Configured Guard '" + identifier + + "' is not created by the Guard API, this is not supported"); + } + GuardImpl guardImpl = ((LazyGuard) guard).instance(); + + return guardImpl.guard(() -> (T) interceptionContext.proceed(), + operation.getReturnType(), meteredOperationName); + } else /* typedGuardInstance.isResolvable() */ { + TypedGuard guard = typedGuardInstance.get(); + if (!(guard instanceof LazyTypedGuard)) { + throw new FaultToleranceException("Configured TypedGuard '" + identifier + + "' is not created by the TypedGuard API, this is not supported"); + } + TypedGuardImpl guardImpl = ((LazyTypedGuard) guard).instance(); + + return guardImpl.guard(() -> { + return (T) interceptionContext.proceed(); + }, meteredOperationName); } } + // V = value type, e.g. String + // AT = async type that eventually produces V, e.g. CompletionStage or Uni private AT asyncFlow(FaultToleranceOperation operation, InvocationContext invocationContext, InterceptionPoint point) { - AsyncSupport asyncSupport = AsyncSupportRegistry.get(operation.getParameterTypes(), operation.getReturnType()); + AsyncSupport asyncSupport = cache.getAsyncSupport(point, operation); if (asyncSupport == null) { throw new FaultToleranceException("Unknown async invocation: " + operation); } - FaultToleranceStrategy strategy = cache.getStrategy(point, () -> prepareAsyncStrategy(operation, point)); + FaultToleranceStrategy strategy = cache.getStrategy(point, () -> prepareStrategy(operation, point)); Invoker invoker = new InterceptionInvoker<>(invocationContext); @@ -249,7 +313,7 @@ private AT asyncFlow(FaultToleranceOperation operation, InvocationContex private V syncFlow(FaultToleranceOperation operation, InvocationContext invocationContext, InterceptionPoint point) throws Throwable { - FaultToleranceStrategy strategy = cache.getStrategy(point, () -> prepareSyncStrategy(operation, point)); + FaultToleranceStrategy strategy = cache.getStrategy(point, () -> prepareStrategy(operation, point)); FaultToleranceContext ctx = faultToleranceContext( () -> Future.from(() -> (V) invocationContext.proceed()), @@ -292,12 +356,14 @@ private FaultToleranceContext faultToleranceContext(Supplier> c return result; } - private FaultToleranceStrategy prepareAsyncStrategy(FaultToleranceOperation operation, InterceptionPoint point) { + private FaultToleranceStrategy prepareStrategy(FaultToleranceOperation operation, InterceptionPoint point) { FaultToleranceStrategy result = invocation(); - result = new RequestScopeActivator<>(result, requestContextController); + if (specCompatibility.isOperationTrulyAsynchronous(operation)) { + result = new RequestScopeActivator<>(result, requestContextController); + } - if (operation.isThreadOffloadRequired()) { + if (specCompatibility.isOperationTrulyAsynchronous(operation) && operation.isThreadOffloadRequired()) { result = new ThreadOffload<>(result, asyncExecutor); } @@ -365,82 +431,13 @@ private FaultToleranceStrategy prepareAsyncStrategy(FaultToleranceOperati result = new MetricsCollector<>(result, metricsProvider.create(meteredOperation), meteredOperation); } - if (!operation.isThreadOffloadRequired()) { + if (specCompatibility.isOperationTrulyAsynchronous(operation) && !operation.isThreadOffloadRequired()) { result = new RememberEventLoop<>(result, eventLoop); } return result; } - private FaultToleranceStrategy prepareSyncStrategy(FaultToleranceOperation operation, InterceptionPoint point) { - FaultToleranceStrategy result = invocation(); - - if (operation.hasBulkhead()) { - result = new Bulkhead<>(result, point.toString(), - operation.getBulkhead().value(), - operation.getBulkhead().waitingTaskQueue()); - } - - if (operation.hasTimeout()) { - result = new Timeout<>(result, point.toString(), - timeInMillis(operation.getTimeout().value(), operation.getTimeout().unit()), - timer); - } - - if (operation.hasRateLimit()) { - result = new RateLimit<>(result, point.toString(), - operation.getRateLimit().value(), - timeInMillis(operation.getRateLimit().window(), operation.getRateLimit().windowUnit()), - timeInMillis(operation.getRateLimit().minSpacing(), operation.getRateLimit().minSpacingUnit()), - operation.getRateLimit().type(), - SystemStopwatch.INSTANCE); - } - - if (operation.hasCircuitBreaker()) { - result = new CircuitBreaker<>(result, point.toString(), - createExceptionDecision(operation.getCircuitBreaker().skipOn(), operation.getCircuitBreaker().failOn()), - timeInMillis(operation.getCircuitBreaker().delay(), operation.getCircuitBreaker().delayUnit()), - operation.getCircuitBreaker().requestVolumeThreshold(), - operation.getCircuitBreaker().failureRatio(), - operation.getCircuitBreaker().successThreshold(), - SystemStopwatch.INSTANCE, - timer); - - String cbName = operation.hasCircuitBreakerName() - ? operation.getCircuitBreakerName().value() - : UUID.randomUUID().toString(); - cbMaintenance.register(cbName, (CircuitBreaker) result); - } - - if (operation.hasRetry()) { - Supplier backoff = prepareRetryBackoff(operation); - - result = new Retry<>(result, point.toString(), - createResultDecision(operation.hasRetryWhen() ? operation.getRetryWhen().result() : null), - createExceptionDecision(operation.getRetry().abortOn(), operation.getRetry().retryOn(), - operation.hasRetryWhen() ? operation.getRetryWhen().exception() : null), - operation.getRetry().maxRetries(), - timeInMillis(operation.getRetry().maxDuration(), operation.getRetry().durationUnit()), - () -> new ThreadSleepDelay(backoff.get()), - () -> new TimerDelay(backoff.get(), timer), - SystemStopwatch.INSTANCE, - operation.hasBeforeRetry() ? prepareBeforeRetryFunction(point, operation) : null); - } - - if (operation.hasFallback()) { - result = new Fallback<>(result, point.toString(), - prepareFallbackFunction(point, operation), - createExceptionDecision(operation.getFallback().skipOn(), operation.getFallback().applyOn())); - } - - if (metricsProvider.isEnabled()) { - MeteredOperation meteredOperation = new CdiMeteredOperationImpl(operation, point, specCompatibility); - result = new MetricsCollector<>(result, metricsProvider.create(meteredOperation), meteredOperation); - } - - return result; - } - private FaultToleranceStrategy> prepareFutureStrategy(FaultToleranceOperation operation, InterceptionPoint point) { FaultToleranceStrategy> result = invocation(); @@ -556,7 +553,7 @@ private Supplier prepareRetryBackoff(FaultToleranceOperation operation) // in asynchronous scenario, T is an async type that eventually produces V private Function> prepareFallbackFunction( InterceptionPoint point, FaultToleranceOperation operation) { - AsyncSupport asyncSupport = AsyncSupportRegistry.get(operation.getParameterTypes(), operation.getReturnType()); + AsyncSupport asyncSupport = cache.getAsyncSupport(point, operation); String fallbackMethodName = operation.getFallback().fallbackMethod(); FallbackMethodCandidates candidates = !"".equals(fallbackMethodName) diff --git a/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/config/ApplyGuardConfig.java b/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/config/ApplyGuardConfig.java new file mode 100644 index 00000000..0f0688d8 --- /dev/null +++ b/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/config/ApplyGuardConfig.java @@ -0,0 +1,20 @@ +package io.smallrye.faulttolerance.config; + +import org.eclipse.microprofile.faulttolerance.exceptions.FaultToleranceDefinitionException; + +import io.smallrye.faulttolerance.api.ApplyGuard; +import io.smallrye.faulttolerance.autoconfig.AutoConfig; +import io.smallrye.faulttolerance.autoconfig.Config; + +@AutoConfig +public interface ApplyGuardConfig extends ApplyGuard, Config { + @Override + default void validate() { + final String INVALID_APPLY_GUARD_ON = "Invalid @ApplyGuard on "; + + if (value().isEmpty()) { + throw new FaultToleranceDefinitionException(INVALID_APPLY_GUARD_ON + method() + + ": value shouldn't be empty"); + } + } +} diff --git a/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/config/FaultToleranceMethods.java b/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/config/FaultToleranceMethods.java index 88a90d30..1dfe26a1 100644 --- a/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/config/FaultToleranceMethods.java +++ b/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/config/FaultToleranceMethods.java @@ -23,6 +23,7 @@ import io.smallrye.common.annotation.Blocking; import io.smallrye.common.annotation.NonBlocking; import io.smallrye.faulttolerance.api.ApplyFaultTolerance; +import io.smallrye.faulttolerance.api.ApplyGuard; import io.smallrye.faulttolerance.api.AsynchronousNonBlocking; import io.smallrye.faulttolerance.api.BeforeRetry; import io.smallrye.faulttolerance.api.CircuitBreakerName; @@ -44,6 +45,7 @@ public static FaultToleranceMethod create(Class beanClass, AnnotatedMethod result.method = createMethodDescriptor(method); result.applyFaultTolerance = getAnnotation(ApplyFaultTolerance.class, method, annotationsPresentDirectly); + result.applyGuard = getAnnotation(ApplyGuard.class, method, annotationsPresentDirectly); result.asynchronous = getAnnotation(Asynchronous.class, method, annotationsPresentDirectly); result.asynchronousNonBlocking = getAnnotation(AsynchronousNonBlocking.class, method, annotationsPresentDirectly); @@ -104,6 +106,7 @@ public static FaultToleranceMethod create(Class beanClass, Method method) { result.method = createMethodDescriptor(method); result.applyFaultTolerance = getAnnotation(ApplyFaultTolerance.class, method, beanClass, annotationsPresentDirectly); + result.applyGuard = getAnnotation(ApplyGuard.class, method, beanClass, annotationsPresentDirectly); result.asynchronous = getAnnotation(Asynchronous.class, method, beanClass, annotationsPresentDirectly); result.asynchronousNonBlocking = getAnnotation(AsynchronousNonBlocking.class, method, beanClass, diff --git a/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/config/FaultToleranceOperation.java b/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/config/FaultToleranceOperation.java index 7d087dfd..da4e5ac3 100644 --- a/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/config/FaultToleranceOperation.java +++ b/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/config/FaultToleranceOperation.java @@ -38,6 +38,7 @@ import io.smallrye.faulttolerance.SpecCompatibility; import io.smallrye.faulttolerance.api.AlwaysOnException; import io.smallrye.faulttolerance.api.ApplyFaultTolerance; +import io.smallrye.faulttolerance.api.ApplyGuard; import io.smallrye.faulttolerance.api.AsynchronousNonBlocking; import io.smallrye.faulttolerance.api.BeforeRetry; import io.smallrye.faulttolerance.api.CircuitBreakerName; @@ -59,6 +60,7 @@ public class FaultToleranceOperation { public static FaultToleranceOperation create(FaultToleranceMethod method) { return new FaultToleranceOperation(method.beanClass, method.method, ApplyFaultToleranceConfigImpl.create(method), + ApplyGuardConfigImpl.create(method), AsynchronousConfigImpl.create(method), AsynchronousNonBlockingConfigImpl.create(method), BlockingConfigImpl.create(method), @@ -84,6 +86,7 @@ public static FaultToleranceOperation create(FaultToleranceMethod method) { private final MethodDescriptor methodDescriptor; private final ApplyFaultToleranceConfig applyFaultTolerance; + private final ApplyGuardConfig applyGuard; private final AsynchronousConfig asynchronous; private final AsynchronousNonBlockingConfig asynchronousNonBlocking; @@ -111,6 +114,7 @@ public static FaultToleranceOperation create(FaultToleranceMethod method) { private FaultToleranceOperation(Class beanClass, MethodDescriptor methodDescriptor, ApplyFaultToleranceConfig applyFaultTolerance, + ApplyGuardConfig applyGuard, AsynchronousConfig asynchronous, AsynchronousNonBlockingConfig asynchronousNonBlocking, BlockingConfig blocking, @@ -134,6 +138,7 @@ private FaultToleranceOperation(Class beanClass, this.methodDescriptor = methodDescriptor; this.applyFaultTolerance = applyFaultTolerance; + this.applyGuard = applyGuard; this.asynchronous = asynchronous; this.asynchronousNonBlocking = asynchronousNonBlocking; @@ -213,6 +218,14 @@ public ApplyFaultTolerance getApplyFaultTolerance() { return applyFaultTolerance; } + public boolean hasApplyGuard() { + return applyGuard != null; + } + + public ApplyGuard getApplyGuard() { + return applyGuard; + } + public boolean hasAsynchronous() { return asynchronous != null; } @@ -430,6 +443,9 @@ public void validate() { if (applyFaultTolerance != null) { applyFaultTolerance.validate(); } + if (applyGuard != null) { + applyGuard.validate(); + } if (asynchronous != null) { asynchronous.validate(); @@ -463,12 +479,20 @@ public void validate() { timeout.validate(); } + validateApplyGuard(); validateFallback(); validateRetryBackoff(); validateRetryWhen(); validateBeforeRetry(); } + private void validateApplyGuard() { + if (applyFaultTolerance != null && applyGuard != null) { + throw new FaultToleranceDefinitionException( + "Both @ApplyFaultTolerance and @ApplyGuard present on " + methodDescriptor); + } + } + private void validateFallback() { if (fallback == null) { return; @@ -575,6 +599,9 @@ public void materialize() { if (applyFaultTolerance != null) { applyFaultTolerance.materialize(); } + if (applyGuard != null) { + applyGuard.materialize(); + } if (asynchronous != null) { asynchronous.materialize(); diff --git a/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/internal/StrategyCache.java b/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/internal/StrategyCache.java index f0594dac..7c6331d2 100644 --- a/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/internal/StrategyCache.java +++ b/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/internal/StrategyCache.java @@ -10,12 +10,15 @@ import io.smallrye.faulttolerance.SpecCompatibility; import io.smallrye.faulttolerance.config.FaultToleranceOperation; import io.smallrye.faulttolerance.core.FaultToleranceStrategy; +import io.smallrye.faulttolerance.core.invocation.AsyncSupport; +import io.smallrye.faulttolerance.core.invocation.AsyncSupportRegistry; @Singleton public class StrategyCache { private final Map> strategies = new ConcurrentHashMap<>(); private final Map fallbackMethods = new ConcurrentHashMap<>(); private final Map beforeRetryMethods = new ConcurrentHashMap<>(); + private final Map> asyncSupports = new ConcurrentHashMap<>(); private final SpecCompatibility specCompatibility; @@ -38,4 +41,9 @@ public FallbackMethodCandidates getFallbackMethodCandidates(InterceptionPoint po public BeforeRetryMethod getBeforeRetryMethod(InterceptionPoint point, FaultToleranceOperation operation) { return beforeRetryMethods.computeIfAbsent(point, ignored -> BeforeRetryMethod.create(operation)); } + + public AsyncSupport getAsyncSupport(InterceptionPoint point, FaultToleranceOperation operation) { + return (AsyncSupport) asyncSupports.computeIfAbsent(point, + ignored -> AsyncSupportRegistry.get(operation.getParameterTypes(), operation.getReturnType())); + } } diff --git a/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/metrics/CdiMeteredOperationImpl.java b/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/metrics/CdiMeteredOperationImpl.java index ba235a5c..4844c380 100644 --- a/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/metrics/CdiMeteredOperationImpl.java +++ b/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/metrics/CdiMeteredOperationImpl.java @@ -18,7 +18,7 @@ public CdiMeteredOperationImpl(FaultToleranceOperation operation, InterceptionPo } @Override - public boolean isAsynchronous() { + public boolean mayBeAsynchronous() { return specCompatibility.isOperationTrulyOrPseudoAsynchronous(operation); } diff --git a/implementation/fault-tolerance/src/main/resources/META-INF/services/io.smallrye.faulttolerance.api.FaultToleranceSpi b/implementation/fault-tolerance/src/main/resources/META-INF/services/io.smallrye.faulttolerance.api.FaultToleranceSpi deleted file mode 100644 index fd1670d9..00000000 --- a/implementation/fault-tolerance/src/main/resources/META-INF/services/io.smallrye.faulttolerance.api.FaultToleranceSpi +++ /dev/null @@ -1 +0,0 @@ -io.smallrye.faulttolerance.CdiFaultToleranceSpi \ No newline at end of file diff --git a/implementation/fault-tolerance/src/main/resources/META-INF/services/io.smallrye.faulttolerance.api.Spi b/implementation/fault-tolerance/src/main/resources/META-INF/services/io.smallrye.faulttolerance.api.Spi new file mode 100644 index 00000000..8a036e09 --- /dev/null +++ b/implementation/fault-tolerance/src/main/resources/META-INF/services/io.smallrye.faulttolerance.api.Spi @@ -0,0 +1 @@ +io.smallrye.faulttolerance.CdiSpi diff --git a/implementation/mutiny/pom.xml b/implementation/mutiny/pom.xml index 2d7ee7fe..844721d2 100644 --- a/implementation/mutiny/pom.xml +++ b/implementation/mutiny/pom.xml @@ -17,10 +17,6 @@ io.smallrye smallrye-fault-tolerance-api - - jakarta.enterprise - jakarta.enterprise.cdi-api - org.eclipse.microprofile.fault-tolerance microprofile-fault-tolerance-api diff --git a/implementation/mutiny/src/main/java/io/smallrye/faulttolerance/mutiny/api/MutinyFaultTolerance.java b/implementation/mutiny/src/main/java/io/smallrye/faulttolerance/mutiny/api/MutinyFaultTolerance.java index 9034c544..ae97609f 100644 --- a/implementation/mutiny/src/main/java/io/smallrye/faulttolerance/mutiny/api/MutinyFaultTolerance.java +++ b/implementation/mutiny/src/main/java/io/smallrye/faulttolerance/mutiny/api/MutinyFaultTolerance.java @@ -5,55 +5,26 @@ import java.util.function.Supplier; import io.smallrye.faulttolerance.api.FaultTolerance; -import io.smallrye.faulttolerance.api.FaultToleranceSpiAccess; +import io.smallrye.faulttolerance.api.SpiAccess; import io.smallrye.mutiny.Uni; /** - * Contains factory methods for {@link FaultTolerance} where the type of value of the guarded action - * is a Mutiny {@link Uni}. These actions are always asynchronous and may be offloaded to another thread - * if necessary. In a modern reactive architecture, which is a typical use case for Mutiny, the actions - * are non-blocking and thread offload is not necessary. - *

- * Note that {@code Uni} is a lazy type, so the guarded actions are not called until the guarded {@code Uni} - * is subscribed to. + * @deprecated use {@link io.smallrye.faulttolerance.api.Guard} or {@link io.smallrye.faulttolerance.api.TypedGuard} */ +@Deprecated(forRemoval = true) public interface MutinyFaultTolerance { - /** - * Returns a builder that, at the end, returns a {@link Callable} guarding the given {@code action}. - * The {@code action} is asynchronous and may be offloaded to another thread. - *

- * Note that {@code Uni} is a lazy type, so the action itself won't execute until the {@code Uni} - * obtained from the resulting {@code Callable} is subscribed to. - */ + @Deprecated(forRemoval = true) static FaultTolerance.Builder, Callable>> createCallable(Callable> action) { - return FaultToleranceSpiAccess.get().newAsyncBuilder(Uni.class, ft -> ft.adaptCallable(action)); + return SpiAccess.get().newAsyncBuilder(Uni.class, ft -> ft.adaptCallable(action)); } - /** - * Returns a builder that, at the end, returns a {@link Supplier} guarding the given {@code action}. - * The {@code action} is asynchronous and may be offloaded to another thread. - *

- * Note that {@code Uni} is a lazy type, so the action itself won't execute until the {@code Uni} - * obtained from the resulting {@code Supplier} is subscribed to. - */ + @Deprecated(forRemoval = true) static FaultTolerance.Builder, Supplier>> createSupplier(Supplier> action) { - return FaultToleranceSpiAccess.get().newAsyncBuilder(Uni.class, ft -> ft.adaptSupplier(action)); + return SpiAccess.get().newAsyncBuilder(Uni.class, ft -> ft.adaptSupplier(action)); } - /** - * Returns a builder that, at the end, returns a {@link FaultTolerance} object representing a set of configured - * fault tolerance strategies. It can be used to execute asynchronous actions using {@link FaultTolerance#call(Callable)} - * or {@link FaultTolerance#get(Supplier)}. - *

- * Note that {@code Uni} is a lazy type, so the action itself won't execute until the {@code Uni} - * returned from the {@code call} or {@code get} methods is subscribed to. For this reason, using - * {@link FaultTolerance#run(Runnable)} doesn't make sense, because there's no way to obtain - * the resulting {@code Uni} that would need subscribing. - *

- * This method usually has to be called with an explicitly provided type argument. For example: - * {@code MutinyFaultTolerance.<String>create()}. - */ + @Deprecated(forRemoval = true) static FaultTolerance.Builder, FaultTolerance>> create() { - return FaultToleranceSpiAccess.get().newAsyncBuilder(Uni.class, Function.identity()); + return SpiAccess.get().newAsyncBuilder(Uni.class, Function.identity()); } } diff --git a/implementation/mutiny/src/test/java/io/smallrye/faulttolerance/mutiny/test/MutinyBulkheadTest.java b/implementation/mutiny/src/test/java/io/smallrye/faulttolerance/mutiny/test/MutinyBulkheadTest.java index b034b96e..796ef14c 100644 --- a/implementation/mutiny/src/test/java/io/smallrye/faulttolerance/mutiny/test/MutinyBulkheadTest.java +++ b/implementation/mutiny/src/test/java/io/smallrye/faulttolerance/mutiny/test/MutinyBulkheadTest.java @@ -7,15 +7,14 @@ import org.eclipse.microprofile.faulttolerance.exceptions.BulkheadException; import org.junit.jupiter.api.Test; -import io.smallrye.faulttolerance.api.FaultTolerance; +import io.smallrye.faulttolerance.api.TypedGuard; import io.smallrye.faulttolerance.core.util.party.Party; -import io.smallrye.faulttolerance.mutiny.api.MutinyFaultTolerance; import io.smallrye.mutiny.Uni; public class MutinyBulkheadTest { @Test public void bulkhead() throws Exception { - FaultTolerance> guarded = MutinyFaultTolerance. create() + TypedGuard> guarded = TypedGuard.create(Types.UNI_STRING) .withBulkhead().limit(5).queueSize(5).done() .withFallback().handler(this::fallback).applyOn(BulkheadException.class).done() .withThreadOffload(true) diff --git a/implementation/mutiny/src/test/java/io/smallrye/faulttolerance/mutiny/test/MutinyCircuitBreakerTest.java b/implementation/mutiny/src/test/java/io/smallrye/faulttolerance/mutiny/test/MutinyCircuitBreakerTest.java index ea7036e2..1b7d5b14 100644 --- a/implementation/mutiny/src/test/java/io/smallrye/faulttolerance/mutiny/test/MutinyCircuitBreakerTest.java +++ b/implementation/mutiny/src/test/java/io/smallrye/faulttolerance/mutiny/test/MutinyCircuitBreakerTest.java @@ -10,23 +10,24 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import io.smallrye.faulttolerance.api.FaultTolerance; +import io.smallrye.faulttolerance.api.CircuitBreakerMaintenance; +import io.smallrye.faulttolerance.api.TypedGuard; import io.smallrye.faulttolerance.core.util.TestException; -import io.smallrye.faulttolerance.mutiny.api.MutinyFaultTolerance; import io.smallrye.mutiny.Uni; public class MutinyCircuitBreakerTest { @BeforeEach public void setUp() { - FaultTolerance.circuitBreakerMaintenance().resetAll(); + CircuitBreakerMaintenance.get().resetAll(); } @Test public void circuitBreaker() { - Supplier> guarded = MutinyFaultTolerance.createSupplier(this::action) + Supplier> guarded = TypedGuard.create(Types.UNI_STRING) .withCircuitBreaker().requestVolumeThreshold(4).done() .withFallback().handler(this::fallback).applyOn(CircuitBreakerOpenException.class).done() - .build(); + .build() + .adaptSupplier(this::action); for (int i = 0; i < 4; i++) { assertThat(guarded.get().subscribeAsCompletionStage()) @@ -42,10 +43,11 @@ public void circuitBreaker() { @Test public void circuitBreakerWithSkipOn() { - Supplier> guarded = MutinyFaultTolerance.createSupplier(this::action) + Supplier> guarded = TypedGuard.create(Types.UNI_STRING) .withCircuitBreaker().requestVolumeThreshold(4).skipOn(TestException.class).done() .withFallback().handler(this::fallback).applyOn(CircuitBreakerOpenException.class).done() - .build(); + .build() + .adaptSupplier(this::action); for (int i = 0; i < 4; i++) { assertThat(guarded.get().subscribeAsCompletionStage()) @@ -62,10 +64,11 @@ public void circuitBreakerWithSkipOn() { @Test public void circuitBreakerWithFailOn() { - Supplier> guarded = MutinyFaultTolerance.createSupplier(this::action) + Supplier> guarded = TypedGuard.create(Types.UNI_STRING) .withCircuitBreaker().requestVolumeThreshold(4).failOn(RuntimeException.class).done() .withFallback().handler(this::fallback).applyOn(CircuitBreakerOpenException.class).done() - .build(); + .build() + .adaptSupplier(this::action); for (int i = 0; i < 4; i++) { assertThat(guarded.get().subscribeAsCompletionStage()) @@ -82,10 +85,11 @@ public void circuitBreakerWithFailOn() { @Test public void circuitBreakerWithWhen() { - Supplier> guarded = MutinyFaultTolerance.createSupplier(this::action) + Supplier> guarded = TypedGuard.create(Types.UNI_STRING) .withCircuitBreaker().requestVolumeThreshold(4).when(e -> e instanceof RuntimeException).done() .withFallback().handler(this::fallback).when(e -> e instanceof CircuitBreakerOpenException).done() - .build(); + .build() + .adaptSupplier(this::action); for (int i = 0; i < 4; i++) { assertThat(guarded.get().subscribeAsCompletionStage()) diff --git a/implementation/mutiny/src/test/java/io/smallrye/faulttolerance/mutiny/test/MutinyFallbackTest.java b/implementation/mutiny/src/test/java/io/smallrye/faulttolerance/mutiny/test/MutinyFallbackTest.java index 3779699f..80b640ec 100644 --- a/implementation/mutiny/src/test/java/io/smallrye/faulttolerance/mutiny/test/MutinyFallbackTest.java +++ b/implementation/mutiny/src/test/java/io/smallrye/faulttolerance/mutiny/test/MutinyFallbackTest.java @@ -8,16 +8,17 @@ import org.junit.jupiter.api.Test; +import io.smallrye.faulttolerance.api.TypedGuard; import io.smallrye.faulttolerance.core.util.TestException; -import io.smallrye.faulttolerance.mutiny.api.MutinyFaultTolerance; import io.smallrye.mutiny.Uni; public class MutinyFallbackTest { @Test public void fallbackWithSupplier() { - Supplier> guarded = MutinyFaultTolerance.createSupplier(this::action) + Supplier> guarded = TypedGuard.create(Types.UNI_STRING) .withFallback().handler(this::fallback).done() - .build(); + .build() + .adaptSupplier(this::action); assertThat(guarded.get().subscribeAsCompletionStage()) .succeedsWithin(10, TimeUnit.SECONDS) @@ -26,9 +27,10 @@ public void fallbackWithSupplier() { @Test public void fallbackWithFunction() { - Supplier> guarded = MutinyFaultTolerance.createSupplier(this::action) + Supplier> guarded = TypedGuard.create(Types.UNI_STRING) .withFallback().handler(e -> Uni.createFrom().item(e.getClass().getSimpleName())).done() - .build(); + .build() + .adaptSupplier(this::action); assertThat(guarded.get().subscribeAsCompletionStage()) .succeedsWithin(10, TimeUnit.SECONDS) @@ -37,9 +39,10 @@ public void fallbackWithFunction() { @Test public void fallbackWithSkipOn() { - Supplier> guarded = MutinyFaultTolerance.createSupplier(this::action) + Supplier> guarded = TypedGuard.create(Types.UNI_STRING) .withFallback().handler(this::fallback).skipOn(TestException.class).done() - .build(); + .build() + .adaptSupplier(this::action); assertThat(guarded.get().subscribeAsCompletionStage()) .failsWithin(10, TimeUnit.SECONDS) @@ -49,9 +52,10 @@ public void fallbackWithSkipOn() { @Test public void fallbackWithApplyOn() { - Supplier> guarded = MutinyFaultTolerance.createSupplier(this::action) + Supplier> guarded = TypedGuard.create(Types.UNI_STRING) .withFallback().handler(this::fallback).applyOn(RuntimeException.class).done() - .build(); + .build() + .adaptSupplier(this::action); assertThat(guarded.get().subscribeAsCompletionStage()) .failsWithin(10, TimeUnit.SECONDS) @@ -61,9 +65,10 @@ public void fallbackWithApplyOn() { @Test public void fallbackWithWhen() { - Supplier> guarded = MutinyFaultTolerance.createSupplier(this::action) + Supplier> guarded = TypedGuard.create(Types.UNI_STRING) .withFallback().handler(this::fallback).when(e -> e instanceof RuntimeException).done() - .build(); + .build() + .adaptSupplier(this::action); assertThat(guarded.get().subscribeAsCompletionStage()) .failsWithin(10, TimeUnit.SECONDS) diff --git a/implementation/mutiny/src/test/java/io/smallrye/faulttolerance/mutiny/test/MutinyPassthroughTest.java b/implementation/mutiny/src/test/java/io/smallrye/faulttolerance/mutiny/test/MutinyPassthroughTest.java index 1e58e251..4e7c3c9d 100644 --- a/implementation/mutiny/src/test/java/io/smallrye/faulttolerance/mutiny/test/MutinyPassthroughTest.java +++ b/implementation/mutiny/src/test/java/io/smallrye/faulttolerance/mutiny/test/MutinyPassthroughTest.java @@ -9,15 +9,14 @@ import org.junit.jupiter.api.Test; -import io.smallrye.faulttolerance.api.FaultTolerance; +import io.smallrye.faulttolerance.api.TypedGuard; import io.smallrye.faulttolerance.core.util.TestException; -import io.smallrye.faulttolerance.mutiny.api.MutinyFaultTolerance; import io.smallrye.mutiny.Uni; public class MutinyPassthroughTest { @Test public void passthroughCompletedSuccessfully() throws Exception { - FaultTolerance> guard = MutinyFaultTolerance. create().build(); + TypedGuard> guard = TypedGuard.create(Types.UNI_STRING).build(); assertThat(guard.call(this::completeSuccessfully).subscribeAsCompletionStage()) .succeedsWithin(10, TimeUnit.SECONDS) @@ -26,7 +25,7 @@ public void passthroughCompletedSuccessfully() throws Exception { @Test public void passthroughCompletedExceptionally() throws Exception { - FaultTolerance> guard = MutinyFaultTolerance. create().build(); + TypedGuard> guard = TypedGuard.create(Types.UNI_STRING).build(); assertThat(guard.call(this::completeExceptionally).subscribeAsCompletionStage()) .failsWithin(10, TimeUnit.SECONDS) @@ -36,7 +35,7 @@ public void passthroughCompletedExceptionally() throws Exception { @Test public void passthroughThrownException() throws Exception { - FaultTolerance> guard = MutinyFaultTolerance. create().build(); + TypedGuard> guard = TypedGuard.create(Types.UNI_STRING).build(); assertThat(guard.call(this::throwException).subscribeAsCompletionStage()) .failsWithin(10, TimeUnit.SECONDS) @@ -46,7 +45,9 @@ public void passthroughThrownException() throws Exception { @Test public void callablePassthroughCompletedSuccessfully() throws Exception { - Callable> guard = MutinyFaultTolerance.createCallable(this::completeSuccessfully).build(); + Callable> guard = TypedGuard.create(Types.UNI_STRING) + .build() + .adaptCallable(this::completeSuccessfully); assertThat(guard.call().subscribeAsCompletionStage()) .succeedsWithin(10, TimeUnit.SECONDS) @@ -55,7 +56,9 @@ public void callablePassthroughCompletedSuccessfully() throws Exception { @Test public void callablePassthroughCompletedExceptionally() throws Exception { - Callable> guard = MutinyFaultTolerance.createCallable(this::completeExceptionally).build(); + Callable> guard = TypedGuard.create(Types.UNI_STRING) + .build() + .adaptCallable(this::completeExceptionally); assertThat(guard.call().subscribeAsCompletionStage()) .failsWithin(10, TimeUnit.SECONDS) @@ -65,7 +68,9 @@ public void callablePassthroughCompletedExceptionally() throws Exception { @Test public void callablePassthroughThrownException() throws Exception { - Callable> guard = MutinyFaultTolerance.createCallable(this::throwException).build(); + Callable> guard = TypedGuard.create(Types.UNI_STRING) + .build() + .adaptCallable(this::throwException); assertThat(guard.call().subscribeAsCompletionStage()) .failsWithin(10, TimeUnit.SECONDS) @@ -75,7 +80,9 @@ public void callablePassthroughThrownException() throws Exception { @Test public void supplierPassthroughCompletedSuccessfully() { - Supplier> guard = MutinyFaultTolerance.createSupplier(this::completeSuccessfully).build(); + Supplier> guard = TypedGuard.create(Types.UNI_STRING) + .build() + .adaptSupplier(this::completeSuccessfully); assertThat(guard.get().subscribeAsCompletionStage()) .succeedsWithin(10, TimeUnit.SECONDS) @@ -84,7 +91,9 @@ public void supplierPassthroughCompletedSuccessfully() { @Test public void supplierPassthroughCompletedExceptionally() { - Supplier> guard = MutinyFaultTolerance.createSupplier(this::completeExceptionally).build(); + Supplier> guard = TypedGuard.create(Types.UNI_STRING) + .build() + .adaptSupplier(this::completeExceptionally); assertThat(guard.get().subscribeAsCompletionStage()) .failsWithin(10, TimeUnit.SECONDS) @@ -94,7 +103,9 @@ public void supplierPassthroughCompletedExceptionally() { @Test public void supplierPassthroughThrownException() { - Supplier> guard = MutinyFaultTolerance.createSupplier(this::throwRuntimeException).build(); + Supplier> guard = TypedGuard.create(Types.UNI_STRING) + .build() + .adaptSupplier(this::throwRuntimeException); assertThat(guard.get().subscribeAsCompletionStage()) .failsWithin(10, TimeUnit.SECONDS) diff --git a/implementation/mutiny/src/test/java/io/smallrye/faulttolerance/mutiny/test/MutinyResubscriptionTest.java b/implementation/mutiny/src/test/java/io/smallrye/faulttolerance/mutiny/test/MutinyResubscriptionTest.java index 36a10555..bd50d19e 100644 --- a/implementation/mutiny/src/test/java/io/smallrye/faulttolerance/mutiny/test/MutinyResubscriptionTest.java +++ b/implementation/mutiny/src/test/java/io/smallrye/faulttolerance/mutiny/test/MutinyResubscriptionTest.java @@ -9,8 +9,8 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import io.smallrye.faulttolerance.api.TypedGuard; import io.smallrye.faulttolerance.core.util.TestException; -import io.smallrye.faulttolerance.mutiny.api.MutinyFaultTolerance; import io.smallrye.mutiny.Uni; public class MutinyResubscriptionTest { @@ -25,9 +25,10 @@ public void setUp() { public void doubleRetry() { // this test verifies resubscription, which is triggered via retry - Supplier> guarded = MutinyFaultTolerance.createSupplier(this::action) + Supplier> guarded = TypedGuard.create(Types.UNI_STRING) .withRetry().maxRetries(3).done() - .build(); + .build() + .adaptSupplier(this::action); Uni hello = guarded.get() .onFailure().retry().atMost(2) diff --git a/implementation/mutiny/src/test/java/io/smallrye/faulttolerance/mutiny/test/MutinyRetryTest.java b/implementation/mutiny/src/test/java/io/smallrye/faulttolerance/mutiny/test/MutinyRetryTest.java index d5548045..b4bfa4ec 100644 --- a/implementation/mutiny/src/test/java/io/smallrye/faulttolerance/mutiny/test/MutinyRetryTest.java +++ b/implementation/mutiny/src/test/java/io/smallrye/faulttolerance/mutiny/test/MutinyRetryTest.java @@ -9,8 +9,8 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import io.smallrye.faulttolerance.api.TypedGuard; import io.smallrye.faulttolerance.core.util.TestException; -import io.smallrye.faulttolerance.mutiny.api.MutinyFaultTolerance; import io.smallrye.mutiny.Uni; public class MutinyRetryTest { @@ -23,10 +23,11 @@ public void setUp() { @Test public void retry() { - Supplier> guarded = MutinyFaultTolerance.createSupplier(this::action) + Supplier> guarded = TypedGuard.create(Types.UNI_STRING) .withRetry().maxRetries(3).done() .withFallback().handler(this::fallback).applyOn(TestException.class).done() - .build(); + .build() + .adaptSupplier(this::action); assertThat(guarded.get().subscribeAsCompletionStage()) .succeedsWithin(10, TimeUnit.SECONDS) @@ -36,10 +37,11 @@ public void retry() { @Test public void retryWithAbortOn() { - Supplier> guarded = MutinyFaultTolerance.createSupplier(this::action) + Supplier> guarded = TypedGuard.create(Types.UNI_STRING) .withRetry().maxRetries(3).abortOn(TestException.class).done() .withFallback().handler(this::fallback).applyOn(TestException.class).done() - .build(); + .build() + .adaptSupplier(this::action); assertThat(guarded.get().subscribeAsCompletionStage()) .succeedsWithin(10, TimeUnit.SECONDS) @@ -49,10 +51,11 @@ public void retryWithAbortOn() { @Test public void retryWithRetryOn() { - Supplier> guarded = MutinyFaultTolerance.createSupplier(this::action) + Supplier> guarded = TypedGuard.create(Types.UNI_STRING) .withRetry().maxRetries(3).retryOn(RuntimeException.class).done() .withFallback().handler(this::fallback).applyOn(TestException.class).done() - .build(); + .build() + .adaptSupplier(this::action); assertThat(guarded.get().subscribeAsCompletionStage()) .succeedsWithin(10, TimeUnit.SECONDS) @@ -62,10 +65,11 @@ public void retryWithRetryOn() { @Test public void retryWithWhen() { - Supplier> guarded = MutinyFaultTolerance.createSupplier(this::action) + Supplier> guarded = TypedGuard.create(Types.UNI_STRING) .withRetry().maxRetries(3).whenException(e -> e instanceof RuntimeException).done() .withFallback().handler(this::fallback).when(e -> e instanceof TestException).done() - .build(); + .build() + .adaptSupplier(this::action); assertThat(guarded.get().subscribeAsCompletionStage()) .succeedsWithin(10, TimeUnit.SECONDS) diff --git a/implementation/mutiny/src/test/java/io/smallrye/faulttolerance/mutiny/test/MutinyTimeoutTest.java b/implementation/mutiny/src/test/java/io/smallrye/faulttolerance/mutiny/test/MutinyTimeoutTest.java index 5972c18d..ef160584 100644 --- a/implementation/mutiny/src/test/java/io/smallrye/faulttolerance/mutiny/test/MutinyTimeoutTest.java +++ b/implementation/mutiny/src/test/java/io/smallrye/faulttolerance/mutiny/test/MutinyTimeoutTest.java @@ -12,16 +12,17 @@ import org.eclipse.microprofile.faulttolerance.exceptions.TimeoutException; import org.junit.jupiter.api.Test; -import io.smallrye.faulttolerance.mutiny.api.MutinyFaultTolerance; +import io.smallrye.faulttolerance.api.TypedGuard; import io.smallrye.mutiny.Uni; public class MutinyTimeoutTest { @Test public void nonblockingAsyncTimeout() throws Exception { - Callable> guarded = MutinyFaultTolerance.createCallable(this::nonblockingAction) + Callable> guarded = TypedGuard.create(Types.UNI_STRING) .withTimeout().duration(1, ChronoUnit.SECONDS).done() .withFallback().applyOn(TimeoutException.class).handler(this::fallback).done() - .build(); + .build() + .adaptCallable(this::nonblockingAction); long time = timed(() -> { assertThat(guarded.call().subscribeAsCompletionStage()) @@ -33,11 +34,12 @@ public void nonblockingAsyncTimeout() throws Exception { @Test public void blockingAsyncTimeout() throws Exception { - Callable> guarded = MutinyFaultTolerance.createCallable(this::blockingAction) + Callable> guarded = TypedGuard.create(Types.UNI_STRING) .withTimeout().duration(1, ChronoUnit.SECONDS).done() .withFallback().applyOn(TimeoutException.class).handler(this::fallback).done() .withThreadOffload(true) // async timeout doesn't interrupt the running thread - .build(); + .build() + .adaptCallable(this::blockingAction); long time = timed(() -> { assertThat(guarded.call().subscribeAsCompletionStage()) diff --git a/implementation/mutiny/src/test/java/io/smallrye/faulttolerance/mutiny/test/MutinyFaultToleranceCastTest.java b/implementation/mutiny/src/test/java/io/smallrye/faulttolerance/mutiny/test/MutinyUntypedGuardTest.java similarity index 58% rename from implementation/mutiny/src/test/java/io/smallrye/faulttolerance/mutiny/test/MutinyFaultToleranceCastTest.java rename to implementation/mutiny/src/test/java/io/smallrye/faulttolerance/mutiny/test/MutinyUntypedGuardTest.java index 63640e3a..1aca9981 100644 --- a/implementation/mutiny/src/test/java/io/smallrye/faulttolerance/mutiny/test/MutinyFaultToleranceCastTest.java +++ b/implementation/mutiny/src/test/java/io/smallrye/faulttolerance/mutiny/test/MutinyUntypedGuardTest.java @@ -1,12 +1,9 @@ package io.smallrye.faulttolerance.mutiny.test; -import static org.assertj.core.api.Assertions.assertThatCode; - import java.time.Duration; import java.time.temporal.ChronoUnit; import java.util.ArrayList; import java.util.List; -import java.util.concurrent.CompletionStage; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; @@ -18,15 +15,14 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import io.smallrye.faulttolerance.api.FaultTolerance; +import io.smallrye.faulttolerance.api.Guard; import io.smallrye.faulttolerance.api.RateLimitException; import io.smallrye.faulttolerance.core.util.TestException; import io.smallrye.faulttolerance.core.util.party.Party; -import io.smallrye.faulttolerance.mutiny.api.MutinyFaultTolerance; import io.smallrye.mutiny.Uni; import io.smallrye.mutiny.helpers.test.UniAssertSubscriber; -public class MutinyFaultToleranceCastTest { +public class MutinyUntypedGuardTest { private ExecutorService executor; @BeforeEach @@ -41,54 +37,28 @@ public void tearDown() throws InterruptedException { } @Test - public void castingCircuitBreaker() throws Exception { - FaultTolerance> guardedString = MutinyFaultTolerance. create() - .withCircuitBreaker().requestVolumeThreshold(6).done() - .build(); - FaultTolerance> guardedInteger = guardedString.castAsync(Uni.class); - - for (int i = 0; i < 3; i++) { - guardedString.call(this::stringAction) - .subscribe().withSubscriber(new UniAssertSubscriber<>()) - .awaitFailure(Duration.ofSeconds(10)) - .assertFailedWith(TestException.class); - - guardedInteger.call(this::integerAction) - .subscribe().withSubscriber(new UniAssertSubscriber<>()) - .awaitFailure(Duration.ofSeconds(10)) - .assertFailedWith(TestException.class); - } - - guardedString.call(this::stringAction) - .subscribe().withSubscriber(new UniAssertSubscriber<>()) - .awaitFailure(Duration.ofSeconds(10)) - .assertFailedWith(CircuitBreakerOpenException.class); - } - - @Test - public void castingBulkhead() throws Exception { - FaultTolerance> guardedString = MutinyFaultTolerance. create() + public void bulkhead() throws Exception { + Guard guard = Guard.create() .withBulkhead().limit(6).queueSize(2).done() .withThreadOffload(true) .build(); - FaultTolerance> guardedInteger = guardedString.castAsync(Uni.class); Party party = Party.create(6); for (int i = 0; i < 4; i++) { - guardedString.call(() -> { + guard.call(() -> { party.participant().attend(); return Uni.createFrom().item("ignored"); - }).subscribe().asCompletionStage(); - guardedInteger.call(() -> { + }, Types.UNI_STRING).subscribe().asCompletionStage(); + guard.call(() -> { party.participant().attend(); return Uni.createFrom().item(42); - }).subscribe().asCompletionStage(); + }, Types.UNI_INTEGER).subscribe().asCompletionStage(); } party.organizer().waitForAll(); - guardedString.call(() -> Uni.createFrom().item("value")) + guard.call(() -> Uni.createFrom().item("value"), Types.UNI_STRING) .subscribe().withSubscriber(new UniAssertSubscriber<>()) .awaitFailure(Duration.ofSeconds(10)) .assertFailedWith(BulkheadException.class); @@ -97,20 +67,43 @@ public void castingBulkhead() throws Exception { } @Test - public void castingRateLimit() throws Exception { - FaultTolerance> guardedString = MutinyFaultTolerance. create() + public void circuitBreaker() throws Exception { + Guard guard = Guard.create() + .withCircuitBreaker().requestVolumeThreshold(6).done() + .build(); + + for (int i = 0; i < 3; i++) { + guard.call(this::stringAction, Types.UNI_STRING) + .subscribe().withSubscriber(new UniAssertSubscriber<>()) + .awaitFailure(Duration.ofSeconds(10)) + .assertFailedWith(TestException.class); + + guard.call(this::integerAction, Types.UNI_INTEGER) + .subscribe().withSubscriber(new UniAssertSubscriber<>()) + .awaitFailure(Duration.ofSeconds(10)) + .assertFailedWith(TestException.class); + } + + guard.call(this::stringAction, Types.UNI_STRING) + .subscribe().withSubscriber(new UniAssertSubscriber<>()) + .awaitFailure(Duration.ofSeconds(10)) + .assertFailedWith(CircuitBreakerOpenException.class); + } + + @Test + public void rateLimit() throws Exception { + Guard guard = Guard.create() .withRateLimit().limit(6).window(1, ChronoUnit.MINUTES).done() .withThreadOffload(true) .build(); - FaultTolerance> guardedInteger = guardedString.castAsync(Uni.class); List> futures = new ArrayList<>(); for (int i = 0; i < 3; i++) { futures.add(executor.submit(() -> { - return guardedString.call(() -> Uni.createFrom().item("ignored")).await().indefinitely(); + return guard.call(() -> Uni.createFrom().item("ignored"), Types.UNI_STRING).await().indefinitely(); })); futures.add(executor.submit(() -> { - return guardedInteger.call(() -> Uni.createFrom().item(42)).await().indefinitely(); + return guard.call(() -> Uni.createFrom().item(42), Types.UNI_INTEGER).await().indefinitely(); })); } @@ -118,35 +111,12 @@ public void castingRateLimit() throws Exception { future.get(); } - guardedString.call(() -> Uni.createFrom().item("value")) + guard.call(() -> Uni.createFrom().item("value"), Types.UNI_STRING) .subscribe().withSubscriber(new UniAssertSubscriber<>()) .awaitFailure(Duration.ofSeconds(10)) .assertFailedWith(RateLimitException.class); } - @Test - public void castingFallback() { - FaultTolerance> guarded = MutinyFaultTolerance. create() - .withFallback().handler(() -> Uni.createFrom().item("fallback")).done() - .build(); - - assertThatCode(() -> guarded.castAsync(Uni.class)).isExactlyInstanceOf(IllegalStateException.class); - } - - @Test - public void castingToSync() { - FaultTolerance> guarded = MutinyFaultTolerance. create().build(); - - assertThatCode(guarded::cast).isExactlyInstanceOf(IllegalStateException.class); - } - - @Test - public void castingToDifferentAsync() { - FaultTolerance> guarded = MutinyFaultTolerance. create().build(); - - assertThatCode(() -> guarded.castAsync(CompletionStage.class)).isExactlyInstanceOf(IllegalStateException.class); - } - public Uni stringAction() { return Uni.createFrom().failure(new TestException()); } diff --git a/implementation/mutiny/src/test/java/io/smallrye/faulttolerance/mutiny/test/Types.java b/implementation/mutiny/src/test/java/io/smallrye/faulttolerance/mutiny/test/Types.java new file mode 100644 index 00000000..bcf58f4e --- /dev/null +++ b/implementation/mutiny/src/test/java/io/smallrye/faulttolerance/mutiny/test/Types.java @@ -0,0 +1,13 @@ +package io.smallrye.faulttolerance.mutiny.test; + +import jakarta.enterprise.util.TypeLiteral; + +import io.smallrye.mutiny.Uni; + +class Types { + static final TypeLiteral> UNI_STRING = new TypeLiteral<>() { + }; + + static final TypeLiteral> UNI_INTEGER = new TypeLiteral<>() { + }; +} diff --git a/implementation/standalone/pom.xml b/implementation/standalone/pom.xml index f2bd4d1e..437c2e95 100644 --- a/implementation/standalone/pom.xml +++ b/implementation/standalone/pom.xml @@ -17,10 +17,6 @@ io.smallrye smallrye-fault-tolerance-api - - jakarta.enterprise - jakarta.enterprise.cdi-api - org.eclipse.microprofile.fault-tolerance microprofile-fault-tolerance-api diff --git a/implementation/standalone/src/main/java/io/smallrye/faulttolerance/standalone/StandaloneFaultToleranceSpi.java b/implementation/standalone/src/main/java/io/smallrye/faulttolerance/standalone/StandaloneSpi.java similarity index 64% rename from implementation/standalone/src/main/java/io/smallrye/faulttolerance/standalone/StandaloneFaultToleranceSpi.java rename to implementation/standalone/src/main/java/io/smallrye/faulttolerance/standalone/StandaloneSpi.java index 39f9c7fe..8706eae5 100644 --- a/implementation/standalone/src/main/java/io/smallrye/faulttolerance/standalone/StandaloneFaultToleranceSpi.java +++ b/implementation/standalone/src/main/java/io/smallrye/faulttolerance/standalone/StandaloneSpi.java @@ -1,14 +1,19 @@ package io.smallrye.faulttolerance.standalone; +import java.lang.reflect.Type; import java.util.function.Function; import io.smallrye.faulttolerance.api.CircuitBreakerMaintenance; import io.smallrye.faulttolerance.api.FaultTolerance; -import io.smallrye.faulttolerance.api.FaultToleranceSpi; +import io.smallrye.faulttolerance.api.Guard; +import io.smallrye.faulttolerance.api.Spi; +import io.smallrye.faulttolerance.api.TypedGuard; import io.smallrye.faulttolerance.core.apiimpl.BuilderLazyDependencies; import io.smallrye.faulttolerance.core.apiimpl.FaultToleranceImpl; +import io.smallrye.faulttolerance.core.apiimpl.GuardImpl; +import io.smallrye.faulttolerance.core.apiimpl.TypedGuardImpl; -public class StandaloneFaultToleranceSpi implements FaultToleranceSpi { +public class StandaloneSpi implements Spi { static class EagerDependenciesHolder { static final EagerDependencies INSTANCE = new EagerDependencies(); } @@ -28,12 +33,25 @@ public int priority() { } @Override + public Guard.Builder newGuardBuilder() { + return new GuardImpl.BuilderImpl(EagerDependenciesHolder.INSTANCE, () -> LazyDependenciesHolder.INSTANCE); + } + + @Override + public TypedGuard.Builder newTypedGuardBuilder(Type type) { + return new TypedGuardImpl.BuilderImpl<>(EagerDependenciesHolder.INSTANCE, () -> LazyDependenciesHolder.INSTANCE, + type); + } + + @Override + @Deprecated(forRemoval = true) public FaultTolerance.Builder newBuilder(Function, R> finisher) { return new FaultToleranceImpl.BuilderImpl<>(EagerDependenciesHolder.INSTANCE, () -> LazyDependenciesHolder.INSTANCE, null, finisher); } @Override + @Deprecated(forRemoval = true) public FaultTolerance.Builder newAsyncBuilder(Class asyncType, Function, R> finisher) { return new FaultToleranceImpl.BuilderImpl<>(EagerDependenciesHolder.INSTANCE, () -> LazyDependenciesHolder.INSTANCE, asyncType, finisher); diff --git a/implementation/standalone/src/main/resources/META-INF/services/io.smallrye.faulttolerance.api.FaultToleranceSpi b/implementation/standalone/src/main/resources/META-INF/services/io.smallrye.faulttolerance.api.FaultToleranceSpi deleted file mode 100644 index 8977e9bf..00000000 --- a/implementation/standalone/src/main/resources/META-INF/services/io.smallrye.faulttolerance.api.FaultToleranceSpi +++ /dev/null @@ -1 +0,0 @@ -io.smallrye.faulttolerance.standalone.StandaloneFaultToleranceSpi \ No newline at end of file diff --git a/implementation/standalone/src/main/resources/META-INF/services/io.smallrye.faulttolerance.api.Spi b/implementation/standalone/src/main/resources/META-INF/services/io.smallrye.faulttolerance.api.Spi new file mode 100644 index 00000000..5e86aa80 --- /dev/null +++ b/implementation/standalone/src/main/resources/META-INF/services/io.smallrye.faulttolerance.api.Spi @@ -0,0 +1 @@ +io.smallrye.faulttolerance.standalone.StandaloneSpi diff --git a/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneBulkheadAsyncEventsTest.java b/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneBulkheadAsyncEventsTest.java index 3742130c..ae87d23a 100644 --- a/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneBulkheadAsyncEventsTest.java +++ b/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneBulkheadAsyncEventsTest.java @@ -11,7 +11,7 @@ import org.eclipse.microprofile.faulttolerance.exceptions.BulkheadException; import org.junit.jupiter.api.Test; -import io.smallrye.faulttolerance.api.FaultTolerance; +import io.smallrye.faulttolerance.api.TypedGuard; import io.smallrye.faulttolerance.core.util.party.Party; public class StandaloneBulkheadAsyncEventsTest { @@ -21,7 +21,7 @@ public void asyncBulkheadEvents() throws Exception { AtomicInteger rejectedCounter = new AtomicInteger(); AtomicInteger finishedCounter = new AtomicInteger(); - FaultTolerance> guarded = FaultTolerance. createAsync() + TypedGuard> guarded = TypedGuard.create(Types.CS_STRING) .withBulkhead() .limit(5) .queueSize(5) diff --git a/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneBulkheadAsyncTest.java b/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneBulkheadAsyncTest.java index 77bdc7b6..a47024ab 100644 --- a/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneBulkheadAsyncTest.java +++ b/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneBulkheadAsyncTest.java @@ -9,13 +9,13 @@ import org.eclipse.microprofile.faulttolerance.exceptions.BulkheadException; import org.junit.jupiter.api.Test; -import io.smallrye.faulttolerance.api.FaultTolerance; +import io.smallrye.faulttolerance.api.TypedGuard; import io.smallrye.faulttolerance.core.util.party.Party; public class StandaloneBulkheadAsyncTest { @Test public void asyncBulkhead() throws Exception { - FaultTolerance> guarded = FaultTolerance. createAsync() + TypedGuard> guarded = TypedGuard.create(Types.CS_STRING) .withBulkhead().limit(5).queueSize(5).done() .withFallback().handler(this::fallback).applyOn(BulkheadException.class).done() .withThreadOffload(true) diff --git a/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneBulkheadEventsTest.java b/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneBulkheadEventsTest.java index ab579e3a..96cb676a 100644 --- a/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneBulkheadEventsTest.java +++ b/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneBulkheadEventsTest.java @@ -13,7 +13,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import io.smallrye.faulttolerance.api.FaultTolerance; +import io.smallrye.faulttolerance.api.TypedGuard; import io.smallrye.faulttolerance.core.util.party.Party; public class StandaloneBulkheadEventsTest { @@ -36,7 +36,7 @@ public void bulkheadEvents() throws Exception { AtomicInteger rejectedCounter = new AtomicInteger(); AtomicInteger finishedCounter = new AtomicInteger(); - FaultTolerance guarded = FaultTolerance. create() + TypedGuard guarded = TypedGuard.create(String.class) .withBulkhead() .limit(5) .onAccepted(acceptedCounter::incrementAndGet) diff --git a/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneBulkheadTest.java b/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneBulkheadTest.java index 6e347bfa..b5e2039f 100644 --- a/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneBulkheadTest.java +++ b/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneBulkheadTest.java @@ -11,7 +11,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import io.smallrye.faulttolerance.api.FaultTolerance; +import io.smallrye.faulttolerance.api.TypedGuard; import io.smallrye.faulttolerance.core.util.party.Party; public class StandaloneBulkheadTest { @@ -30,7 +30,7 @@ public void tearDown() throws InterruptedException { @Test public void bulkhead() throws Exception { - FaultTolerance guarded = FaultTolerance. create() + TypedGuard guarded = TypedGuard.create(String.class) .withBulkhead().limit(5).done() .withFallback().handler(this::fallback).applyOn(BulkheadException.class).done() .build(); diff --git a/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneCircuitBreakerAsyncEventsTest.java b/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneCircuitBreakerAsyncEventsTest.java index 27e2e8df..cb304ed8 100644 --- a/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneCircuitBreakerAsyncEventsTest.java +++ b/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneCircuitBreakerAsyncEventsTest.java @@ -15,8 +15,9 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import io.smallrye.faulttolerance.api.CircuitBreakerMaintenance; import io.smallrye.faulttolerance.api.CircuitBreakerState; -import io.smallrye.faulttolerance.api.FaultTolerance; +import io.smallrye.faulttolerance.api.TypedGuard; import io.smallrye.faulttolerance.core.util.TestException; public class StandaloneCircuitBreakerAsyncEventsTest { @@ -24,7 +25,7 @@ public class StandaloneCircuitBreakerAsyncEventsTest { @BeforeEach public void setUp() { - FaultTolerance.circuitBreakerMaintenance().resetAll(); + CircuitBreakerMaintenance.get().resetAll(); } @Test @@ -34,7 +35,7 @@ public void asyncCircuitBreakerEvents() throws Exception { AtomicInteger preventedCounter = new AtomicInteger(); AtomicReference changedState = new AtomicReference<>(); - Supplier> guarded = FaultTolerance.createAsyncSupplier(this::action) + Supplier> guarded = TypedGuard.create(Types.CS_STRING) .withCircuitBreaker() .requestVolumeThreshold(4) .onSuccess(successCounter::incrementAndGet) @@ -43,7 +44,8 @@ public void asyncCircuitBreakerEvents() throws Exception { .onStateChange(changedState::set) .done() .withFallback().handler(this::fallback).applyOn(CircuitBreakerOpenException.class).done() - .build(); + .build() + .adaptSupplier(this::action); actionShouldFail = false; for (int i = 0; i < 2; i++) { diff --git a/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneCircuitBreakerAsyncTest.java b/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneCircuitBreakerAsyncTest.java index 9f30df89..581aa477 100644 --- a/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneCircuitBreakerAsyncTest.java +++ b/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneCircuitBreakerAsyncTest.java @@ -13,21 +13,23 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import io.smallrye.faulttolerance.api.FaultTolerance; +import io.smallrye.faulttolerance.api.CircuitBreakerMaintenance; +import io.smallrye.faulttolerance.api.TypedGuard; import io.smallrye.faulttolerance.core.util.TestException; public class StandaloneCircuitBreakerAsyncTest { @BeforeEach public void setUp() { - FaultTolerance.circuitBreakerMaintenance().resetAll(); + CircuitBreakerMaintenance.get().resetAll(); } @Test public void asyncCircuitBreaker() { - Supplier> guarded = FaultTolerance.createAsyncSupplier(this::action) + Supplier> guarded = TypedGuard.create(Types.CS_STRING) .withCircuitBreaker().requestVolumeThreshold(4).done() .withFallback().handler(this::fallback).applyOn(CircuitBreakerOpenException.class).done() - .build(); + .build() + .adaptSupplier(this::action); for (int i = 0; i < 4; i++) { assertThat(guarded.get()) @@ -43,10 +45,11 @@ public void asyncCircuitBreaker() { @Test public void asyncCircuitBreakerWithSkipOn() { - Supplier> guarded = FaultTolerance.createAsyncSupplier(this::action) + Supplier> guarded = TypedGuard.create(Types.CS_STRING) .withCircuitBreaker().requestVolumeThreshold(4).skipOn(TestException.class).done() .withFallback().handler(this::fallback).applyOn(CircuitBreakerOpenException.class).done() - .build(); + .build() + .adaptSupplier(this::action); for (int i = 0; i < 4; i++) { assertThat(guarded.get()) @@ -63,10 +66,11 @@ public void asyncCircuitBreakerWithSkipOn() { @Test public void asyncCircuitBreakerWithFailOn() { - Supplier> guarded = FaultTolerance.createAsyncSupplier(this::action) + Supplier> guarded = TypedGuard.create(Types.CS_STRING) .withCircuitBreaker().requestVolumeThreshold(4).failOn(RuntimeException.class).done() .withFallback().handler(this::fallback).applyOn(CircuitBreakerOpenException.class).done() - .build(); + .build() + .adaptSupplier(this::action); for (int i = 0; i < 4; i++) { assertThat(guarded.get()) @@ -83,10 +87,11 @@ public void asyncCircuitBreakerWithFailOn() { @Test public void asyncCircuitBreakerWithWhen() { - Supplier> guarded = FaultTolerance.createAsyncSupplier(this::action) + Supplier> guarded = TypedGuard.create(Types.CS_STRING) .withCircuitBreaker().requestVolumeThreshold(4).when(e -> e instanceof RuntimeException).done() .withFallback().handler(this::fallback).when(e -> e instanceof CircuitBreakerOpenException).done() - .build(); + .build() + .adaptSupplier(this::action); for (int i = 0; i < 4; i++) { assertThat(guarded.get()) diff --git a/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneCircuitBreakerEventsTest.java b/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneCircuitBreakerEventsTest.java index 354e0640..dbcddfcf 100644 --- a/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneCircuitBreakerEventsTest.java +++ b/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneCircuitBreakerEventsTest.java @@ -11,8 +11,9 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import io.smallrye.faulttolerance.api.CircuitBreakerMaintenance; import io.smallrye.faulttolerance.api.CircuitBreakerState; -import io.smallrye.faulttolerance.api.FaultTolerance; +import io.smallrye.faulttolerance.api.TypedGuard; import io.smallrye.faulttolerance.core.util.TestException; public class StandaloneCircuitBreakerEventsTest { @@ -20,7 +21,7 @@ public class StandaloneCircuitBreakerEventsTest { @BeforeEach public void setUp() { - FaultTolerance.circuitBreakerMaintenance().resetAll(); + CircuitBreakerMaintenance.get().resetAll(); } @Test @@ -30,7 +31,7 @@ public void circuitBreakerEvents() throws Exception { AtomicInteger preventedCounter = new AtomicInteger(); AtomicReference changedState = new AtomicReference<>(); - Callable guarded = FaultTolerance.createCallable(this::action) + Callable guarded = TypedGuard.create(String.class) .withCircuitBreaker() .requestVolumeThreshold(4) .onSuccess(successCounter::incrementAndGet) @@ -39,7 +40,8 @@ public void circuitBreakerEvents() throws Exception { .onStateChange(changedState::set) .done() .withFallback().handler(this::fallback).applyOn(CircuitBreakerOpenException.class).done() - .build(); + .build() + .adaptCallable(this::action); actionShouldFail = false; for (int i = 0; i < 2; i++) { diff --git a/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneCircuitBreakerMaintenanceTest.java b/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneCircuitBreakerMaintenanceTest.java index 6354e3dc..98817953 100644 --- a/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneCircuitBreakerMaintenanceTest.java +++ b/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneCircuitBreakerMaintenanceTest.java @@ -13,28 +13,30 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import io.smallrye.faulttolerance.api.FaultTolerance; +import io.smallrye.faulttolerance.api.CircuitBreakerMaintenance; +import io.smallrye.faulttolerance.api.TypedGuard; import io.smallrye.faulttolerance.core.util.TestException; public class StandaloneCircuitBreakerMaintenanceTest { @BeforeEach public void setUp() { - FaultTolerance.circuitBreakerMaintenance().resetAll(); + CircuitBreakerMaintenance.get().resetAll(); } @Test public void circuitBreakerEvents() throws Exception { assertThatThrownBy(() -> { - FaultTolerance.circuitBreakerMaintenance().currentState("my-cb"); + CircuitBreakerMaintenance.get().currentState("my-cb"); }); - Callable guarded = FaultTolerance.createCallable(this::action) + Callable guarded = TypedGuard.create(String.class) .withCircuitBreaker().requestVolumeThreshold(4).delay(1, ChronoUnit.SECONDS).name("my-cb").done() .withFallback().handler(this::fallback).applyOn(CircuitBreakerOpenException.class).done() - .build(); + .build() + .adaptCallable(this::action); AtomicInteger stateChanges = new AtomicInteger(); - FaultTolerance.circuitBreakerMaintenance().onStateChange("my-cb", ignored -> stateChanges.incrementAndGet()); + CircuitBreakerMaintenance.get().onStateChange("my-cb", ignored -> stateChanges.incrementAndGet()); assertThat(stateChanges).hasValue(0); diff --git a/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneCircuitBreakerTest.java b/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneCircuitBreakerTest.java index 81587c3d..037412a4 100644 --- a/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneCircuitBreakerTest.java +++ b/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneCircuitBreakerTest.java @@ -9,21 +9,23 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import io.smallrye.faulttolerance.api.FaultTolerance; +import io.smallrye.faulttolerance.api.CircuitBreakerMaintenance; +import io.smallrye.faulttolerance.api.TypedGuard; import io.smallrye.faulttolerance.core.util.TestException; public class StandaloneCircuitBreakerTest { @BeforeEach public void setUp() { - FaultTolerance.circuitBreakerMaintenance().resetAll(); + CircuitBreakerMaintenance.get().resetAll(); } @Test public void circuitBreaker() throws Exception { - Callable guarded = FaultTolerance.createCallable(this::action) + Callable guarded = TypedGuard.create(String.class) .withCircuitBreaker().requestVolumeThreshold(4).done() .withFallback().handler(this::fallback).applyOn(CircuitBreakerOpenException.class).done() - .build(); + .build() + .adaptCallable(this::action); for (int i = 0; i < 4; i++) { assertThatCode(guarded::call).isExactlyInstanceOf(TestException.class); @@ -34,10 +36,11 @@ public void circuitBreaker() throws Exception { @Test public void circuitBreakerWithSkipOn() { - Callable guarded = FaultTolerance.createCallable(this::action) + Callable guarded = TypedGuard.create(String.class) .withCircuitBreaker().requestVolumeThreshold(4).skipOn(TestException.class).done() .withFallback().handler(this::fallback).applyOn(CircuitBreakerOpenException.class).done() - .build(); + .build() + .adaptCallable(this::action); for (int i = 0; i < 4; i++) { assertThatCode(guarded::call).isExactlyInstanceOf(TestException.class); @@ -48,10 +51,11 @@ public void circuitBreakerWithSkipOn() { @Test public void circuitBreakerWithFailOn() { - Callable guarded = FaultTolerance.createCallable(this::action) + Callable guarded = TypedGuard.create(String.class) .withCircuitBreaker().requestVolumeThreshold(4).failOn(RuntimeException.class).done() .withFallback().handler(this::fallback).applyOn(CircuitBreakerOpenException.class).done() - .build(); + .build() + .adaptCallable(this::action); for (int i = 0; i < 4; i++) { assertThatCode(guarded::call).isExactlyInstanceOf(TestException.class); @@ -62,10 +66,11 @@ public void circuitBreakerWithFailOn() { @Test public void circuitBreakerWithWhen() { - Callable guarded = FaultTolerance.createCallable(this::action) + Callable guarded = TypedGuard.create(String.class) .withCircuitBreaker().requestVolumeThreshold(4).when(e -> e instanceof RuntimeException).done() .withFallback().handler(this::fallback).when(e -> e instanceof CircuitBreakerOpenException).done() - .build(); + .build() + .adaptCallable(this::action); for (int i = 0; i < 4; i++) { assertThatCode(guarded::call).isExactlyInstanceOf(TestException.class); diff --git a/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneFallbackAsyncTest.java b/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneFallbackAsyncTest.java index 4a82b5f8..74954515 100644 --- a/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneFallbackAsyncTest.java +++ b/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneFallbackAsyncTest.java @@ -11,15 +11,16 @@ import org.junit.jupiter.api.Test; -import io.smallrye.faulttolerance.api.FaultTolerance; +import io.smallrye.faulttolerance.api.TypedGuard; import io.smallrye.faulttolerance.core.util.TestException; public class StandaloneFallbackAsyncTest { @Test public void asyncFallbackWithSupplier() { - Supplier> guarded = FaultTolerance.createAsyncSupplier(this::action) + Supplier> guarded = TypedGuard.create(Types.CS_STRING) .withFallback().handler(this::fallback).done() - .build(); + .build() + .adaptSupplier(this::action); assertThat(guarded.get()) .succeedsWithin(10, TimeUnit.SECONDS) @@ -28,9 +29,10 @@ public void asyncFallbackWithSupplier() { @Test public void asyncFallbackWithFunction() { - Supplier> guarded = FaultTolerance.createAsyncSupplier(this::action) + Supplier> guarded = TypedGuard.create(Types.CS_STRING) .withFallback().handler(e -> completedFuture(e.getClass().getSimpleName())).done() - .build(); + .build() + .adaptSupplier(this::action); assertThat(guarded.get()) .succeedsWithin(10, TimeUnit.SECONDS) @@ -39,9 +41,10 @@ public void asyncFallbackWithFunction() { @Test public void asyncFallbackWithSkipOn() { - Supplier> guarded = FaultTolerance.createAsyncSupplier(this::action) + Supplier> guarded = TypedGuard.create(Types.CS_STRING) .withFallback().handler(this::fallback).skipOn(TestException.class).done() - .build(); + .build() + .adaptSupplier(this::action); assertThat(guarded.get()) .failsWithin(10, TimeUnit.SECONDS) @@ -51,9 +54,10 @@ public void asyncFallbackWithSkipOn() { @Test public void asyncFallbackWithApplyOn() { - Supplier> guarded = FaultTolerance.createAsyncSupplier(this::action) + Supplier> guarded = TypedGuard.create(Types.CS_STRING) .withFallback().handler(this::fallback).applyOn(RuntimeException.class).done() - .build(); + .build() + .adaptSupplier(this::action); assertThat(guarded.get()) .failsWithin(10, TimeUnit.SECONDS) @@ -63,23 +67,10 @@ public void asyncFallbackWithApplyOn() { @Test public void asyncFallbackWithWhen() { - Supplier> guarded = FaultTolerance.createAsyncSupplier(this::action) + Supplier> guarded = TypedGuard.create(Types.CS_STRING) .withFallback().handler(this::fallback).when(e -> e instanceof RuntimeException).done() - .build(); - - assertThat(guarded.get()) - .failsWithin(10, TimeUnit.SECONDS) - .withThrowableOfType(ExecutionException.class) // caused by AssertJ calling future.get() - .withCauseExactlyInstanceOf(TestException.class); - } - - @Test - public void synchronousFlow() { - // doing this is usually a mistake, because it only guards the synchronous execution - // only testing it here to verify that indeed asynchronous fault tolerance doesn't apply - Supplier> guarded = FaultTolerance.createSupplier(this::action) - .withFallback().handler(this::fallback).done() - .build(); + .build() + .adaptSupplier(this::action); assertThat(guarded.get()) .failsWithin(10, TimeUnit.SECONDS) diff --git a/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneFallbackTest.java b/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneFallbackTest.java index 4a64adcf..f4023972 100644 --- a/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneFallbackTest.java +++ b/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneFallbackTest.java @@ -7,51 +7,56 @@ import org.junit.jupiter.api.Test; -import io.smallrye.faulttolerance.api.FaultTolerance; +import io.smallrye.faulttolerance.api.TypedGuard; import io.smallrye.faulttolerance.core.util.TestException; public class StandaloneFallbackTest { @Test public void fallbackWithSupplier() throws Exception { - Callable guarded = FaultTolerance.createCallable(this::action) + Callable guarded = TypedGuard.create(String.class) .withFallback().handler(this::fallback).done() - .build(); + .build() + .adaptCallable(this::action); assertThat(guarded.call()).isEqualTo("fallback"); } @Test public void fallbackWithFunction() throws Exception { - Callable guarded = FaultTolerance.createCallable(this::action) + Callable guarded = TypedGuard.create(String.class) .withFallback().handler(e -> e.getClass().getSimpleName()).done() - .build(); + .build() + .adaptCallable(this::action); assertThat(guarded.call()).isEqualTo("TestException"); } @Test public void fallbackWithSkipOn() { - Callable guarded = FaultTolerance.createCallable(this::action) + Callable guarded = TypedGuard.create(String.class) .withFallback().handler(this::fallback).skipOn(TestException.class).done() - .build(); + .build() + .adaptCallable(this::action); assertThatCode(guarded::call).isExactlyInstanceOf(TestException.class); } @Test public void fallbackWithApplyOn() { - Callable guarded = FaultTolerance.createCallable(this::action) + Callable guarded = TypedGuard.create(String.class) .withFallback().handler(this::fallback).applyOn(RuntimeException.class).done() - .build(); + .build() + .adaptCallable(this::action); assertThatCode(guarded::call).isExactlyInstanceOf(TestException.class); } @Test public void fallbackWithWhen() { - Callable guarded = FaultTolerance.createCallable(this::action) + Callable guarded = TypedGuard.create(String.class) .withFallback().handler(this::fallback).when(e -> e instanceof RuntimeException).done() - .build(); + .build() + .adaptCallable(this::action); assertThatCode(guarded::call).isExactlyInstanceOf(TestException.class); } diff --git a/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneMetricsTest.java b/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneMetricsTest.java index c53309cb..e97f1525 100644 --- a/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneMetricsTest.java +++ b/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneMetricsTest.java @@ -15,7 +15,7 @@ import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.Tag; import io.micrometer.core.instrument.simple.SimpleMeterRegistry; -import io.smallrye.faulttolerance.api.FaultTolerance; +import io.smallrye.faulttolerance.api.TypedGuard; import io.smallrye.faulttolerance.core.metrics.MetricsConstants; import io.smallrye.faulttolerance.core.util.TestException; import io.smallrye.faulttolerance.standalone.Configuration; @@ -63,11 +63,12 @@ public static void tearDown() throws InterruptedException { @Test public void test() throws Exception { - Callable guarded = FaultTolerance.createCallable(this::action) + Callable guarded = TypedGuard.create(String.class) .withDescription(NAME) .withFallback().handler(this::fallback).done() .withRetry().maxRetries(3).done() - .build(); + .build() + .adaptCallable(this::action); assertThat(guarded.call()).isEqualTo("fallback"); diff --git a/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneMetricsTimerTest.java b/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneMetricsTimerTest.java index 60930698..66b8102f 100644 --- a/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneMetricsTimerTest.java +++ b/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneMetricsTimerTest.java @@ -17,7 +17,7 @@ import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.simple.SimpleMeterRegistry; -import io.smallrye.faulttolerance.api.FaultTolerance; +import io.smallrye.faulttolerance.api.TypedGuard; import io.smallrye.faulttolerance.core.metrics.MetricsConstants; import io.smallrye.faulttolerance.core.util.barrier.Barrier; import io.smallrye.faulttolerance.standalone.Configuration; @@ -67,11 +67,12 @@ public static void tearDown() throws InterruptedException { @Test public void test() throws Exception { - Callable> guarded = FaultTolerance.createAsyncCallable(this::action) + Callable> guarded = TypedGuard.create(Types.CS_STRING) .withThreadOffload(true) .withTimeout().duration(1, ChronoUnit.MINUTES).done() .withFallback().handler(this::fallback).done() - .build(); + .build() + .adaptCallable(this::action); CompletableFuture future = guarded.call().toCompletableFuture(); diff --git a/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandalonePassthroughAsyncTest.java b/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandalonePassthroughAsyncTest.java index c053d2df..81cc873a 100644 --- a/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandalonePassthroughAsyncTest.java +++ b/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandalonePassthroughAsyncTest.java @@ -3,7 +3,6 @@ import static java.util.concurrent.CompletableFuture.completedFuture; import static java.util.concurrent.CompletableFuture.failedFuture; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatCode; import java.util.concurrent.Callable; import java.util.concurrent.CompletionStage; @@ -13,13 +12,13 @@ import org.junit.jupiter.api.Test; -import io.smallrye.faulttolerance.api.FaultTolerance; +import io.smallrye.faulttolerance.api.TypedGuard; import io.smallrye.faulttolerance.core.util.TestException; public class StandalonePassthroughAsyncTest { @Test public void asyncPassthroughCompletedSuccessfully() throws Exception { - FaultTolerance> guard = FaultTolerance. createAsync().build(); + TypedGuard> guard = TypedGuard.create(Types.CS_STRING).build(); assertThat(guard.call(this::completeSuccessfully)) .succeedsWithin(10, TimeUnit.SECONDS) @@ -28,7 +27,7 @@ public void asyncPassthroughCompletedSuccessfully() throws Exception { @Test public void asyncPassthroughCompletedExceptionally() throws Exception { - FaultTolerance> guard = FaultTolerance. createAsync().build(); + TypedGuard> guard = TypedGuard.create(Types.CS_STRING).build(); assertThat(guard.call(this::completeExceptionally)) .failsWithin(10, TimeUnit.SECONDS) @@ -38,7 +37,7 @@ public void asyncPassthroughCompletedExceptionally() throws Exception { @Test public void asyncPassthroughThrownException() throws Exception { - FaultTolerance> guard = FaultTolerance. createAsync().build(); + TypedGuard> guard = TypedGuard.create(Types.CS_STRING).build(); assertThat(guard.call(this::throwException)) .failsWithin(10, TimeUnit.SECONDS) @@ -48,7 +47,8 @@ public void asyncPassthroughThrownException() throws Exception { @Test public void callableAsyncPassthroughCompletedSuccessfully() throws Exception { - Callable> guard = FaultTolerance.createAsyncCallable(this::completeSuccessfully).build(); + Callable> guard = TypedGuard.create(Types.CS_STRING).build() + .adaptCallable(this::completeSuccessfully); assertThat(guard.call()) .succeedsWithin(10, TimeUnit.SECONDS) @@ -57,7 +57,8 @@ public void callableAsyncPassthroughCompletedSuccessfully() throws Exception { @Test public void callableAsyncPassthroughCompletedExceptionally() throws Exception { - Callable> guard = FaultTolerance.createAsyncCallable(this::completeExceptionally).build(); + Callable> guard = TypedGuard.create(Types.CS_STRING).build() + .adaptCallable(this::completeExceptionally); assertThat(guard.call()) .failsWithin(10, TimeUnit.SECONDS) @@ -67,7 +68,8 @@ public void callableAsyncPassthroughCompletedExceptionally() throws Exception { @Test public void callableAsyncPassthroughThrownException() throws Exception { - Callable> guard = FaultTolerance.createAsyncCallable(this::throwException).build(); + Callable> guard = TypedGuard.create(Types.CS_STRING).build() + .adaptCallable(this::throwException); assertThat(guard.call()) .failsWithin(10, TimeUnit.SECONDS) @@ -77,7 +79,8 @@ public void callableAsyncPassthroughThrownException() throws Exception { @Test public void supplierAsyncPassthroughCompletedSuccessfully() { - Supplier> guard = FaultTolerance.createAsyncSupplier(this::completeSuccessfully).build(); + Supplier> guard = TypedGuard.create(Types.CS_STRING).build() + .adaptSupplier(this::completeSuccessfully); assertThat(guard.get()) .succeedsWithin(10, TimeUnit.SECONDS) @@ -86,7 +89,8 @@ public void supplierAsyncPassthroughCompletedSuccessfully() { @Test public void supplierAsyncPassthroughCompletedExceptionally() { - Supplier> guard = FaultTolerance.createAsyncSupplier(this::completeExceptionally).build(); + Supplier> guard = TypedGuard.create(Types.CS_STRING).build() + .adaptSupplier(this::completeExceptionally); assertThat(guard.get()) .failsWithin(10, TimeUnit.SECONDS) @@ -96,7 +100,8 @@ public void supplierAsyncPassthroughCompletedExceptionally() { @Test public void supplierAsyncPassthroughThrownException() { - Supplier> guard = FaultTolerance.createAsyncSupplier(this::throwRuntimeException).build(); + Supplier> guard = TypedGuard.create(Types.CS_STRING).build() + .adaptSupplier(this::throwRuntimeException); assertThat(guard.get()) .failsWithin(10, TimeUnit.SECONDS) @@ -104,30 +109,6 @@ public void supplierAsyncPassthroughThrownException() { .withCauseExactlyInstanceOf(RuntimeException.class); } - @Test - public void runnableAsyncPassthroughCompletedSuccessfully() { - Runnable guard = FaultTolerance.createAsyncRunnable(this::completeSuccessfully).build(); - - assertThatCode(guard::run).doesNotThrowAnyException(); - } - - @Test - public void runnableAsyncPassthroughCompletedExceptionally() { - Runnable guard = FaultTolerance.createAsyncRunnable(this::completeExceptionally).build(); - - assertThatCode(guard::run).doesNotThrowAnyException(); - } - - @Test - public void runnableAsyncPassthroughThrownException() { - Runnable guard = FaultTolerance.createAsyncRunnable(this::throwRuntimeException).build(); - - // internally, a CompletableFuture is created that is completed with the thrown exception - // and since the action is a Runnable, that CompletableFuture is just dropped - // (in other words, "async runnable" is just "fire and forget") - assertThatCode(guard::run).doesNotThrowAnyException(); - } - private CompletionStage completeSuccessfully() { return completedFuture("value"); } @@ -141,6 +122,6 @@ private CompletionStage throwException() throws TestException { } private CompletionStage throwRuntimeException() { - throw new RuntimeException(new TestException()); + throw new RuntimeException(); } } diff --git a/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandalonePassthroughTest.java b/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandalonePassthroughTest.java index b3a5ba95..3de528ae 100644 --- a/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandalonePassthroughTest.java +++ b/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandalonePassthroughTest.java @@ -8,22 +8,21 @@ import org.junit.jupiter.api.Test; -import io.smallrye.faulttolerance.api.FaultTolerance; +import io.smallrye.faulttolerance.api.TypedGuard; import io.smallrye.faulttolerance.core.util.TestException; public class StandalonePassthroughTest { @Test public void passthroughValue() throws Exception { - FaultTolerance guard = FaultTolerance. create().build(); + TypedGuard guard = TypedGuard.create(String.class).build(); assertThat(guard.call(this::returnValue)).isEqualTo("value"); assertThat(guard.get(this::returnValue)).isEqualTo("value"); - assertThatCode(() -> guard.run(this::returnValue)).doesNotThrowAnyException(); } @Test public void passthroughRuntimeException() { - FaultTolerance guard = FaultTolerance.create().build(); + TypedGuard guard = TypedGuard.create(Object.class).build(); assertThatCode(() -> guard.call(this::throwRuntimeException)) .isExactlyInstanceOf(RuntimeException.class) @@ -32,15 +31,11 @@ public void passthroughRuntimeException() { assertThatCode(() -> guard.get(this::throwRuntimeException)) .isExactlyInstanceOf(RuntimeException.class) .hasCauseExactlyInstanceOf(TestException.class); - - assertThatCode(() -> guard.run(this::throwRuntimeException)) - .isExactlyInstanceOf(RuntimeException.class) - .hasCauseExactlyInstanceOf(TestException.class); } @Test public void passthroughException() { - FaultTolerance guard = FaultTolerance.create().build(); + TypedGuard guard = TypedGuard.create(Object.class).build(); assertThatCode(() -> guard.call(this::throwException)) .isExactlyInstanceOf(TestException.class); @@ -48,14 +43,16 @@ public void passthroughException() { @Test public void callablePassthroughValue() throws Exception { - Callable guard = FaultTolerance.createCallable(this::returnValue).build(); + Callable guard = TypedGuard.create(String.class).build() + .adaptCallable(this::returnValue); assertThat(guard.call()).isEqualTo("value"); } @Test public void callablePassthroughRuntimeException() { - Callable guard = FaultTolerance.createCallable(this::throwRuntimeException).build(); + Callable guard = TypedGuard.create(String.class).build() + .adaptCallable(this::throwRuntimeException); assertThatCode(guard::call) .isExactlyInstanceOf(RuntimeException.class) @@ -64,7 +61,8 @@ public void callablePassthroughRuntimeException() { @Test public void callablePassthroughException() { - Callable guard = FaultTolerance.createCallable(this::throwException).build(); + Callable guard = TypedGuard.create(String.class).build() + .adaptCallable(this::throwException); assertThatCode(guard::call) .isExactlyInstanceOf(TestException.class); @@ -72,36 +70,22 @@ public void callablePassthroughException() { @Test public void supplierPassthroughValue() { - Supplier guard = FaultTolerance.createSupplier(this::returnValue).build(); + Supplier guard = TypedGuard.create(String.class).build() + .adaptSupplier(this::returnValue); assertThat(guard.get()).isEqualTo("value"); } @Test public void supplierPassthroughRuntimeException() { - Supplier guard = FaultTolerance.createSupplier(this::throwRuntimeException).build(); + Supplier guard = TypedGuard.create(String.class).build() + .adaptSupplier(this::throwRuntimeException); assertThatCode(guard::get) .isExactlyInstanceOf(RuntimeException.class) .hasCauseExactlyInstanceOf(TestException.class); } - @Test - public void runnablePassthroughValue() { - Runnable guard = FaultTolerance.createRunnable(this::returnValue).build(); - - assertThatCode(guard::run).doesNotThrowAnyException(); - } - - @Test - public void runnablePassthroughRuntimeException() { - Runnable guard = FaultTolerance.createRunnable(this::throwRuntimeException).build(); - - assertThatCode(guard::run) - .isExactlyInstanceOf(RuntimeException.class) - .hasCauseExactlyInstanceOf(TestException.class); - } - private String returnValue() { return "value"; } diff --git a/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneRateLimitAsyncEventsTest.java b/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneRateLimitAsyncEventsTest.java index f03bf0b4..c5533fc2 100644 --- a/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneRateLimitAsyncEventsTest.java +++ b/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneRateLimitAsyncEventsTest.java @@ -17,8 +17,8 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import io.smallrye.faulttolerance.api.FaultTolerance; import io.smallrye.faulttolerance.api.RateLimitException; +import io.smallrye.faulttolerance.api.TypedGuard; public class StandaloneRateLimitAsyncEventsTest { private ExecutorService executor; @@ -39,7 +39,7 @@ public void asyncBulkhead() throws Exception { AtomicInteger permittedCounter = new AtomicInteger(); AtomicInteger rejectedCounter = new AtomicInteger(); - FaultTolerance> guarded = FaultTolerance. createAsync() + TypedGuard> guarded = TypedGuard.create(Types.CS_STRING) .withRateLimit() .limit(5) .window(1, ChronoUnit.MINUTES) diff --git a/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneRateLimitAsyncTest.java b/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneRateLimitAsyncTest.java index 08e66e52..a489fe70 100644 --- a/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneRateLimitAsyncTest.java +++ b/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneRateLimitAsyncTest.java @@ -16,8 +16,8 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import io.smallrye.faulttolerance.api.FaultTolerance; import io.smallrye.faulttolerance.api.RateLimitException; +import io.smallrye.faulttolerance.api.TypedGuard; public class StandaloneRateLimitAsyncTest { private ExecutorService executor; @@ -35,7 +35,7 @@ public void tearDown() throws InterruptedException { @Test public void asyncBulkhead() throws Exception { - FaultTolerance> guarded = FaultTolerance. createAsync() + TypedGuard> guarded = TypedGuard.create(Types.CS_STRING) .withRateLimit().limit(5).window(1, ChronoUnit.MINUTES).done() .withFallback().handler(this::fallback).applyOn(RateLimitException.class).done() .withThreadOffload(true) diff --git a/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneRateLimitEventsTest.java b/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneRateLimitEventsTest.java index 3fb27022..5fe37329 100644 --- a/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneRateLimitEventsTest.java +++ b/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneRateLimitEventsTest.java @@ -15,8 +15,8 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import io.smallrye.faulttolerance.api.FaultTolerance; import io.smallrye.faulttolerance.api.RateLimitException; +import io.smallrye.faulttolerance.api.TypedGuard; public class StandaloneRateLimitEventsTest { private ExecutorService executor; @@ -37,7 +37,7 @@ public void rateLimit() throws Exception { AtomicInteger permittedCounter = new AtomicInteger(); AtomicInteger rejectedCounter = new AtomicInteger(); - FaultTolerance guarded = FaultTolerance. create() + TypedGuard guarded = TypedGuard.create(String.class) .withRateLimit() .limit(5) .window(1, ChronoUnit.MINUTES) diff --git a/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneRateLimitTest.java b/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneRateLimitTest.java index c97e6928..8c3126d9 100644 --- a/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneRateLimitTest.java +++ b/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneRateLimitTest.java @@ -14,8 +14,8 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import io.smallrye.faulttolerance.api.FaultTolerance; import io.smallrye.faulttolerance.api.RateLimitException; +import io.smallrye.faulttolerance.api.TypedGuard; public class StandaloneRateLimitTest { private ExecutorService executor; @@ -33,7 +33,7 @@ public void tearDown() throws InterruptedException { @Test public void rateLimit() throws Exception { - FaultTolerance guarded = FaultTolerance. create() + TypedGuard guarded = TypedGuard.create(String.class) .withRateLimit().limit(5).window(1, ChronoUnit.MINUTES).done() .withFallback().handler(this::fallback).applyOn(RateLimitException.class).done() .build(); diff --git a/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneRetryAsyncEventsTest.java b/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneRetryAsyncEventsTest.java index 355e722e..81e6ef85 100644 --- a/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneRetryAsyncEventsTest.java +++ b/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneRetryAsyncEventsTest.java @@ -11,7 +11,7 @@ import org.junit.jupiter.api.Test; -import io.smallrye.faulttolerance.api.FaultTolerance; +import io.smallrye.faulttolerance.api.TypedGuard; import io.smallrye.faulttolerance.core.util.TestException; public class StandaloneRetryAsyncEventsTest { @@ -23,7 +23,7 @@ public void asyncRetry() { AtomicInteger successCounter = new AtomicInteger(); AtomicInteger failureCounter = new AtomicInteger(); - Supplier> guarded = FaultTolerance.createAsyncSupplier(this::action) + Supplier> guarded = TypedGuard.create(Types.CS_STRING) .withRetry() .maxRetries(3) .onRetry(retryCounter::incrementAndGet) @@ -31,7 +31,8 @@ public void asyncRetry() { .onFailure(failureCounter::incrementAndGet) .done() .withFallback().handler(this::fallback).applyOn(TestException.class).done() - .build(); + .build() + .adaptSupplier(this::action); failTimes = 10; assertThat(guarded.get()) diff --git a/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneRetryAsyncTest.java b/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneRetryAsyncTest.java index e485dcea..68ec7cf0 100644 --- a/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneRetryAsyncTest.java +++ b/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneRetryAsyncTest.java @@ -6,7 +6,6 @@ import java.util.Objects; import java.util.concurrent.CompletionStage; -import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Supplier; @@ -14,7 +13,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import io.smallrye.faulttolerance.api.FaultTolerance; +import io.smallrye.faulttolerance.api.TypedGuard; import io.smallrye.faulttolerance.core.util.TestException; public class StandaloneRetryAsyncTest { @@ -27,10 +26,11 @@ public void setUp() { @Test public void asyncRetry() { - Supplier> guarded = FaultTolerance.createAsyncSupplier(this::actionFail) + Supplier> guarded = TypedGuard.create(Types.CS_STRING) .withRetry().maxRetries(3).done() .withFallback().handler(this::fallback).applyOn(TestException.class).done() - .build(); + .build() + .adaptSupplier(this::actionFail); assertThat(guarded.get()) .succeedsWithin(10, TimeUnit.SECONDS) @@ -40,10 +40,11 @@ public void asyncRetry() { @Test public void asyncRetryWithAbortOn() { - Supplier> guarded = FaultTolerance.createAsyncSupplier(this::actionFail) + Supplier> guarded = TypedGuard.create(Types.CS_STRING) .withRetry().maxRetries(3).abortOn(TestException.class).done() .withFallback().handler(this::fallback).applyOn(TestException.class).done() - .build(); + .build() + .adaptSupplier(this::actionFail); assertThat(guarded.get()) .succeedsWithin(10, TimeUnit.SECONDS) @@ -53,10 +54,11 @@ public void asyncRetryWithAbortOn() { @Test public void asyncRetryWithRetryOn() { - Supplier> guarded = FaultTolerance.createAsyncSupplier(this::actionFail) + Supplier> guarded = TypedGuard.create(Types.CS_STRING) .withRetry().maxRetries(3).retryOn(RuntimeException.class).done() .withFallback().handler(this::fallback).applyOn(TestException.class).done() - .build(); + .build() + .adaptSupplier(this::actionFail); assertThat(guarded.get()) .succeedsWithin(10, TimeUnit.SECONDS) @@ -66,10 +68,11 @@ public void asyncRetryWithRetryOn() { @Test public void asyncRetryWithWhenException() { - Supplier> guarded = FaultTolerance.createAsyncSupplier(this::actionFail) + Supplier> guarded = TypedGuard.create(Types.CS_STRING) .withRetry().maxRetries(3).whenException(e -> e instanceof RuntimeException).done() .withFallback().handler(this::fallback).when(e -> e instanceof TestException).done() - .build(); + .build() + .adaptSupplier(this::actionFail); assertThat(guarded.get()) .succeedsWithin(10, TimeUnit.SECONDS) @@ -79,10 +82,11 @@ public void asyncRetryWithWhenException() { @Test public void asyncRetryWithWhenResult() { - Supplier> guarded = FaultTolerance.createAsyncSupplier(this::actionReturnNull) + Supplier> guarded = TypedGuard.create(Types.CS_STRING) .withRetry().maxRetries(3).whenResult(Objects::isNull).done() .withFallback().handler(this::fallback).done() - .build(); + .build() + .adaptSupplier(this::actionReturnNull); assertThat(guarded.get()) .succeedsWithin(10, TimeUnit.SECONDS) @@ -90,22 +94,6 @@ public void asyncRetryWithWhenResult() { assertThat(counter).hasValue(4); // 1 initial invocation + 3 retries } - @Test - public void synchronousFlow() { - // this is usually a mistake, because it only guards the synchronous execution - // only testing it here to verify that indeed asynchronous fault tolerance doesn't apply - Supplier> guarded = FaultTolerance.createSupplier(this::actionFail) - .withRetry().maxRetries(3).abortOn(TestException.class).done() - .withFallback().handler(this::fallback).done() - .build(); - - assertThat(guarded.get()) - .failsWithin(10, TimeUnit.SECONDS) - .withThrowableOfType(ExecutionException.class) // caused by AssertJ calling future.get() - .withCauseExactlyInstanceOf(TestException.class); - assertThat(counter).hasValue(1); // 1 initial invocation - } - public CompletionStage actionFail() { counter.incrementAndGet(); return failedFuture(new TestException()); diff --git a/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneRetryEventsTest.java b/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneRetryEventsTest.java index 26a4e4b0..fa4f3dbe 100644 --- a/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneRetryEventsTest.java +++ b/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneRetryEventsTest.java @@ -7,7 +7,7 @@ import org.junit.jupiter.api.Test; -import io.smallrye.faulttolerance.api.FaultTolerance; +import io.smallrye.faulttolerance.api.TypedGuard; import io.smallrye.faulttolerance.core.util.TestException; public class StandaloneRetryEventsTest { @@ -19,7 +19,7 @@ public void retryEvents() throws Exception { AtomicInteger successCounter = new AtomicInteger(); AtomicInteger failureCounter = new AtomicInteger(); - Callable guarded = FaultTolerance.createCallable(this::action) + Callable guarded = TypedGuard.create(String.class) .withRetry() .maxRetries(3) .onRetry(retryCounter::incrementAndGet) @@ -27,7 +27,8 @@ public void retryEvents() throws Exception { .onFailure(failureCounter::incrementAndGet) .done() .withFallback().applyOn(TestException.class).handler(this::fallback).done() - .build(); + .build() + .adaptCallable(this::action); failTimes = 10; assertThat(guarded.call()).isEqualTo("fallback"); diff --git a/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneRetryTest.java b/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneRetryTest.java index da170aa2..db833a4b 100644 --- a/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneRetryTest.java +++ b/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneRetryTest.java @@ -8,7 +8,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import io.smallrye.faulttolerance.api.FaultTolerance; +import io.smallrye.faulttolerance.api.TypedGuard; import io.smallrye.faulttolerance.core.util.TestException; public class StandaloneRetryTest { @@ -21,10 +21,11 @@ public void setUp() { @Test public void retry() throws Exception { - Callable guarded = FaultTolerance.createCallable(this::actionThrow) + Callable guarded = TypedGuard.create(String.class) .withRetry().maxRetries(3).done() .withFallback().applyOn(TestException.class).handler(this::fallback).done() - .build(); + .build() + .adaptCallable(this::actionThrow); assertThat(guarded.call()).isEqualTo("fallback"); assertThat(counter).isEqualTo(4); // 1 initial invocation + 3 retries @@ -32,10 +33,11 @@ public void retry() throws Exception { @Test public void retryWithAbortOn() throws Exception { - Callable guarded = FaultTolerance.createCallable(this::actionThrow) + Callable guarded = TypedGuard.create(String.class) .withRetry().maxRetries(3).abortOn(TestException.class).done() .withFallback().applyOn(TestException.class).handler(this::fallback).done() - .build(); + .build() + .adaptCallable(this::actionThrow); assertThat(guarded.call()).isEqualTo("fallback"); assertThat(counter).isEqualTo(1); // 1 initial invocation @@ -43,10 +45,11 @@ public void retryWithAbortOn() throws Exception { @Test public void retryWithRetryOn() throws Exception { - Callable guarded = FaultTolerance.createCallable(this::actionThrow) + Callable guarded = TypedGuard.create(String.class) .withRetry().maxRetries(3).retryOn(RuntimeException.class).done() .withFallback().applyOn(TestException.class).handler(this::fallback).done() - .build(); + .build() + .adaptCallable(this::actionThrow); assertThat(guarded.call()).isEqualTo("fallback"); assertThat(counter).isEqualTo(1); // 1 initial invocation @@ -54,10 +57,11 @@ public void retryWithRetryOn() throws Exception { @Test public void retryWithWhenException() throws Exception { - Callable guarded = FaultTolerance.createCallable(this::actionThrow) + Callable guarded = TypedGuard.create(String.class) .withRetry().maxRetries(3).whenException(e -> e instanceof RuntimeException).done() .withFallback().when(e -> e instanceof TestException).handler(this::fallback).done() - .build(); + .build() + .adaptCallable(this::actionThrow); assertThat(guarded.call()).isEqualTo("fallback"); assertThat(counter).isEqualTo(1); // 1 initial invocation @@ -65,10 +69,11 @@ public void retryWithWhenException() throws Exception { @Test public void retryWithWhenResult() throws Exception { - Callable guarded = FaultTolerance.createCallable(this::actionReturnNull) + Callable guarded = TypedGuard.create(String.class) .withRetry().maxRetries(3).whenResult(Objects::isNull).done() .withFallback().handler(this::fallback).done() - .build(); + .build() + .adaptCallable(this::actionReturnNull); assertThat(guarded.call()).isEqualTo("fallback"); assertThat(counter).isEqualTo(4); // 1 initial invocation + 3 retries diff --git a/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneThreadOffloadTest.java b/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneThreadOffloadTest.java index 9743c2ce..e813bcf3 100644 --- a/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneThreadOffloadTest.java +++ b/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneThreadOffloadTest.java @@ -14,13 +14,13 @@ import org.junit.jupiter.api.Test; -import io.smallrye.faulttolerance.api.FaultTolerance; +import io.smallrye.faulttolerance.api.TypedGuard; import io.smallrye.faulttolerance.core.util.party.Party; public class StandaloneThreadOffloadTest { @Test public void integratedExecutor() throws Exception { - FaultTolerance> guarded = FaultTolerance. createAsync() + TypedGuard> guarded = TypedGuard.create(Types.CS_STRING) .withThreadOffload(true) .build(); @@ -48,7 +48,7 @@ public void explicitExecutor() throws Exception { ExecutorService executor = Executors.newCachedThreadPool(runnable -> new Thread(runnable, prefix + "_" + counter.incrementAndGet())); - FaultTolerance> guarded = FaultTolerance. createAsync() + TypedGuard> guarded = TypedGuard.create(Types.CS_STRING) .withThreadOffload(true) .withThreadOffloadExecutor(executor) .build(); diff --git a/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneTimeoutAsyncEventsTest.java b/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneTimeoutAsyncEventsTest.java index 7b1edd20..26eb9e56 100644 --- a/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneTimeoutAsyncEventsTest.java +++ b/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneTimeoutAsyncEventsTest.java @@ -12,7 +12,7 @@ import org.eclipse.microprofile.faulttolerance.exceptions.TimeoutException; import org.junit.jupiter.api.Test; -import io.smallrye.faulttolerance.api.FaultTolerance; +import io.smallrye.faulttolerance.api.TypedGuard; public class StandaloneTimeoutAsyncEventsTest { private volatile boolean shouldSleep; @@ -22,7 +22,7 @@ public void asyncTimeoutEvents() throws Exception { AtomicInteger timeoutCounter = new AtomicInteger(); AtomicInteger finishedCounter = new AtomicInteger(); - Callable> guarded = FaultTolerance.createAsyncCallable(this::action) + Callable> guarded = TypedGuard.create(Types.CS_STRING) .withTimeout() .duration(1, ChronoUnit.SECONDS) .onTimeout(timeoutCounter::incrementAndGet) @@ -30,7 +30,8 @@ public void asyncTimeoutEvents() throws Exception { .done() .withFallback().applyOn(TimeoutException.class).handler(this::fallback).done() .withThreadOffload(true) // async timeout doesn't interrupt the running thread - .build(); + .build() + .adaptCallable(this::action); shouldSleep = true; assertThat(guarded.call()) diff --git a/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneTimeoutAsyncTest.java b/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneTimeoutAsyncTest.java index 1b9c6b0f..7f01a6f6 100644 --- a/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneTimeoutAsyncTest.java +++ b/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneTimeoutAsyncTest.java @@ -13,16 +13,17 @@ import org.eclipse.microprofile.faulttolerance.exceptions.TimeoutException; import org.junit.jupiter.api.Test; -import io.smallrye.faulttolerance.api.FaultTolerance; +import io.smallrye.faulttolerance.api.TypedGuard; public class StandaloneTimeoutAsyncTest { @Test public void asyncTimeout() throws Exception { - Callable> guarded = FaultTolerance.createAsyncCallable(this::action) + Callable> guarded = TypedGuard.create(Types.CS_STRING) .withTimeout().duration(1, ChronoUnit.SECONDS).done() .withFallback().applyOn(TimeoutException.class).handler(this::fallback).done() .withThreadOffload(true) // async timeout doesn't interrupt the running thread - .build(); + .build() + .adaptCallable(this::action); long time = timed(() -> { assertThat(guarded.call()) diff --git a/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneTimeoutEventsTest.java b/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneTimeoutEventsTest.java index 51d3a4ed..c8843b48 100644 --- a/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneTimeoutEventsTest.java +++ b/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneTimeoutEventsTest.java @@ -9,7 +9,7 @@ import org.eclipse.microprofile.faulttolerance.exceptions.TimeoutException; import org.junit.jupiter.api.Test; -import io.smallrye.faulttolerance.api.FaultTolerance; +import io.smallrye.faulttolerance.api.TypedGuard; public class StandaloneTimeoutEventsTest { private boolean shouldSleep; @@ -19,14 +19,15 @@ public void timeoutEvents() throws Exception { AtomicInteger timeoutCounter = new AtomicInteger(); AtomicInteger finishedCounter = new AtomicInteger(); - Callable guarded = FaultTolerance.createCallable(this::action) + Callable guarded = TypedGuard.create(String.class) .withTimeout() .duration(1000, ChronoUnit.MILLIS) .onTimeout(timeoutCounter::incrementAndGet) .onFinished(finishedCounter::incrementAndGet) .done() .withFallback().applyOn(TimeoutException.class).handler(this::fallback).done() - .build(); + .build() + .adaptCallable(this::action); shouldSleep = true; assertThat(guarded.call()).isEqualTo("fallback"); diff --git a/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneTimeoutTest.java b/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneTimeoutTest.java index 76c48570..b945feb7 100644 --- a/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneTimeoutTest.java +++ b/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneTimeoutTest.java @@ -10,15 +10,16 @@ import org.eclipse.microprofile.faulttolerance.exceptions.TimeoutException; import org.junit.jupiter.api.Test; -import io.smallrye.faulttolerance.api.FaultTolerance; +import io.smallrye.faulttolerance.api.TypedGuard; public class StandaloneTimeoutTest { @Test public void timeout() throws Exception { - Callable guarded = FaultTolerance.createCallable(this::action) + Callable guarded = TypedGuard.create(String.class) .withTimeout().duration(1000, ChronoUnit.MILLIS).done() .withFallback().applyOn(TimeoutException.class).handler(this::fallback).done() - .build(); + .build() + .adaptCallable(this::action); long time = timed(() -> { assertThat(guarded.call()).isEqualTo("fallback"); diff --git a/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneFaultToleranceCastAsyncTest.java b/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneUntypedAsyncGuardTest.java similarity index 62% rename from implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneFaultToleranceCastAsyncTest.java rename to implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneUntypedAsyncGuardTest.java index 208651e5..047dffb2 100644 --- a/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneFaultToleranceCastAsyncTest.java +++ b/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneUntypedAsyncGuardTest.java @@ -3,7 +3,6 @@ import static java.util.concurrent.CompletableFuture.completedFuture; import static java.util.concurrent.CompletableFuture.failedFuture; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatCode; import java.time.temporal.ChronoUnit; import java.util.ArrayList; @@ -21,12 +20,12 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import io.smallrye.faulttolerance.api.FaultTolerance; +import io.smallrye.faulttolerance.api.Guard; import io.smallrye.faulttolerance.api.RateLimitException; import io.smallrye.faulttolerance.core.util.TestException; import io.smallrye.faulttolerance.core.util.party.Party; -public class StandaloneFaultToleranceCastAsyncTest { +public class StandaloneUntypedAsyncGuardTest { private ExecutorService executor; @BeforeEach @@ -41,54 +40,28 @@ public void tearDown() throws InterruptedException { } @Test - public void castingAsyncCircuitBreaker() throws Exception { - FaultTolerance> guardedString = FaultTolerance. createAsync() - .withCircuitBreaker().requestVolumeThreshold(6).done() - .build(); - FaultTolerance> guardedInteger = guardedString.castAsync(CompletionStage.class); - - for (int i = 0; i < 3; i++) { - assertThat(guardedString.call(this::stringAction)) - .failsWithin(10, TimeUnit.SECONDS) - .withThrowableOfType(ExecutionException.class) // caused by AssertJ calling future.get() - .withCauseExactlyInstanceOf(TestException.class); - - assertThat(guardedInteger.call(this::integerAction)) - .failsWithin(10, TimeUnit.SECONDS) - .withThrowableOfType(ExecutionException.class) // caused by AssertJ calling future.get() - .withCauseExactlyInstanceOf(TestException.class); - } - - assertThat(guardedString.call(this::stringAction)) - .failsWithin(10, TimeUnit.SECONDS) - .withThrowableOfType(ExecutionException.class) // caused by AssertJ calling future.get() - .withCauseExactlyInstanceOf(CircuitBreakerOpenException.class); - } - - @Test - public void castingAsyncBulkhead() throws Exception { - FaultTolerance> guardedString = FaultTolerance. createAsync() + public void bulkhead() throws Exception { + Guard guard = Guard.create() .withBulkhead().limit(6).queueSize(2).done() .withThreadOffload(true) .build(); - FaultTolerance> guardedInteger = guardedString.castAsync(CompletionStage.class); Party party = Party.create(6); for (int i = 0; i < 4; i++) { - guardedString.call(() -> { + guard.call(() -> { party.participant().attend(); return completedFuture("ignored"); - }); - guardedInteger.call(() -> { + }, Types.CS_STRING); + guard.call(() -> { party.participant().attend(); return completedFuture(42); - }); + }, Types.CS_INTEGER); } party.organizer().waitForAll(); - assertThat(guardedString.call(() -> completedFuture("value"))) + assertThat(guard.call(() -> completedFuture("value"), Types.CS_STRING)) .failsWithin(10, TimeUnit.SECONDS) .withThrowableOfType(ExecutionException.class) .withCauseExactlyInstanceOf(BulkheadException.class); @@ -97,20 +70,43 @@ public void castingAsyncBulkhead() throws Exception { } @Test - public void castingAsyncRateLimit() throws Exception { - FaultTolerance> guardedString = FaultTolerance. createAsync() + public void circuitBreaker() throws Exception { + Guard guard = Guard.create() + .withCircuitBreaker().requestVolumeThreshold(6).done() + .build(); + + for (int i = 0; i < 3; i++) { + assertThat(guard.call(this::stringAction, Types.CS_STRING)) + .failsWithin(10, TimeUnit.SECONDS) + .withThrowableOfType(ExecutionException.class) // caused by AssertJ calling future.get() + .withCauseExactlyInstanceOf(TestException.class); + + assertThat(guard.call(this::integerAction, Types.CS_INTEGER)) + .failsWithin(10, TimeUnit.SECONDS) + .withThrowableOfType(ExecutionException.class) // caused by AssertJ calling future.get() + .withCauseExactlyInstanceOf(TestException.class); + } + + assertThat(guard.call(this::stringAction, Types.CS_STRING)) + .failsWithin(10, TimeUnit.SECONDS) + .withThrowableOfType(ExecutionException.class) // caused by AssertJ calling future.get() + .withCauseExactlyInstanceOf(CircuitBreakerOpenException.class); + } + + @Test + public void rateLimit() throws Exception { + Guard guard = Guard.create() .withRateLimit().limit(6).window(1, ChronoUnit.MINUTES).done() .withThreadOffload(true) .build(); - FaultTolerance> guardedInteger = guardedString.castAsync(CompletionStage.class); List> futures = new ArrayList<>(); for (int i = 0; i < 3; i++) { futures.add(executor.submit(() -> { - return guardedString.call(() -> completedFuture("ignored")).toCompletableFuture().get(); + return guard.call(() -> completedFuture("ignored"), Types.CS_STRING).toCompletableFuture().get(); })); futures.add(executor.submit(() -> { - return guardedInteger.call(() -> completedFuture(42)).toCompletableFuture().get(); + return guard.call(() -> completedFuture(42), Types.CS_INTEGER).toCompletableFuture().get(); })); } @@ -118,28 +114,12 @@ public void castingAsyncRateLimit() throws Exception { future.get(); } - assertThat(guardedString.call(() -> completedFuture("value"))) + assertThat(guard.call(() -> completedFuture("value"), Types.CS_STRING)) .failsWithin(10, TimeUnit.SECONDS) .withThrowableOfType(ExecutionException.class) .withCauseExactlyInstanceOf(RateLimitException.class); } - @Test - public void castingAsyncFallback() { - FaultTolerance> guarded = FaultTolerance. createAsync() - .withFallback().handler(() -> completedFuture("fallback")).done() - .build(); - - assertThatCode(() -> guarded.castAsync(CompletionStage.class)).isExactlyInstanceOf(IllegalStateException.class); - } - - @Test - public void castingToSync() { - FaultTolerance> guarded = FaultTolerance. createAsync().build(); - - assertThatCode(guarded::cast).isExactlyInstanceOf(IllegalStateException.class); - } - public CompletionStage stringAction() { return failedFuture(new TestException()); } diff --git a/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneFaultToleranceCastTest.java b/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneUntypedGuardTest.java similarity index 53% rename from implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneFaultToleranceCastTest.java rename to implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneUntypedGuardTest.java index d0ae37b0..58c799a6 100644 --- a/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneFaultToleranceCastTest.java +++ b/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneUntypedGuardTest.java @@ -5,7 +5,6 @@ import java.time.temporal.ChronoUnit; import java.util.ArrayList; import java.util.List; -import java.util.concurrent.CompletionStage; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -18,12 +17,12 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import io.smallrye.faulttolerance.api.FaultTolerance; +import io.smallrye.faulttolerance.api.Guard; import io.smallrye.faulttolerance.api.RateLimitException; import io.smallrye.faulttolerance.core.util.TestException; import io.smallrye.faulttolerance.core.util.party.Party; -public class StandaloneFaultToleranceCastTest { +public class StandaloneUntypedGuardTest { private ExecutorService executor; @BeforeEach @@ -38,65 +37,63 @@ public void tearDown() throws InterruptedException { } @Test - public void castingCircuitBreaker() { - FaultTolerance guardedString = FaultTolerance. create() - .withCircuitBreaker().requestVolumeThreshold(6).done() - .build(); - FaultTolerance guardedInteger = guardedString.cast(); - - for (int i = 0; i < 3; i++) { - assertThatCode(() -> guardedString.call(this::stringAction)).isExactlyInstanceOf(TestException.class); - assertThatCode(() -> guardedInteger.call(this::integerAction)).isExactlyInstanceOf(TestException.class); - } - - assertThatCode(() -> guardedString.call(this::stringAction)).isExactlyInstanceOf(CircuitBreakerOpenException.class); - } - - @Test - public void castingBulkhead() throws Exception { - FaultTolerance guardedString = FaultTolerance. create() + public void bulkhead() throws Exception { + Guard guard = Guard.create() .withBulkhead().limit(6).done() .build(); - FaultTolerance guardedInteger = guardedString.cast(); Party party = Party.create(6); for (int i = 0; i < 3; i++) { executor.submit(() -> { - return guardedString.call(() -> { + return guard.call(() -> { party.participant().attend(); return "ignored"; - }); + }, String.class); }); executor.submit(() -> { - return guardedInteger.call(() -> { + return guard.call(() -> { party.participant().attend(); return 42; - }); + }, Integer.class); }); } party.organizer().waitForAll(); - assertThatCode(() -> guardedString.call(() -> "value")).isExactlyInstanceOf(BulkheadException.class); + assertThatCode(() -> guard.call(() -> "value", String.class)).isExactlyInstanceOf(BulkheadException.class); party.organizer().disband(); } @Test - public void castingRateLimit() throws ExecutionException, InterruptedException { - FaultTolerance guardedString = FaultTolerance. create() + public void circuitBreaker() { + Guard guarded = Guard.create() + .withCircuitBreaker().requestVolumeThreshold(6).done() + .build(); + + for (int i = 0; i < 3; i++) { + assertThatCode(() -> guarded.call(this::stringAction, String.class)).isExactlyInstanceOf(TestException.class); + assertThatCode(() -> guarded.call(this::integerAction, Integer.class)).isExactlyInstanceOf(TestException.class); + } + + assertThatCode(() -> guarded.call(this::stringAction, String.class)) + .isExactlyInstanceOf(CircuitBreakerOpenException.class); + } + + @Test + public void rateLimit() throws ExecutionException, InterruptedException { + Guard guard = Guard.create() .withRateLimit().limit(6).window(1, ChronoUnit.MINUTES).done() .build(); - FaultTolerance guardedInteger = guardedString.cast(); List> futures = new ArrayList<>(); for (int i = 0; i < 3; i++) { futures.add(executor.submit(() -> { - return guardedString.call(() -> "ignored"); + return guard.call(() -> "ignored", String.class); })); futures.add(executor.submit(() -> { - return guardedInteger.call(() -> 42); + return guard.call(() -> 42, Integer.class); })); } @@ -104,21 +101,7 @@ public void castingRateLimit() throws ExecutionException, InterruptedException { future.get(); } - assertThatCode(() -> guardedString.call(() -> "value")).isExactlyInstanceOf(RateLimitException.class); - } - - @Test - public void castingFallback() { - FaultTolerance guarded = FaultTolerance. create() - .withFallback().handler(() -> "fallback").done() - .build(); - assertThatCode(guarded::cast).isExactlyInstanceOf(IllegalStateException.class); - } - - @Test - public void castingToAsync() { - FaultTolerance guarded = FaultTolerance. create().build(); - assertThatCode(() -> guarded.castAsync(CompletionStage.class)).isExactlyInstanceOf(IllegalStateException.class); + assertThatCode(() -> guard.call(() -> "value", String.class)).isExactlyInstanceOf(RateLimitException.class); } public String stringAction() throws TestException { diff --git a/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/Types.java b/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/Types.java new file mode 100644 index 00000000..450169b4 --- /dev/null +++ b/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/Types.java @@ -0,0 +1,13 @@ +package io.smallrye.faulttolerance.standalone.test; + +import java.util.concurrent.CompletionStage; + +import jakarta.enterprise.util.TypeLiteral; + +class Types { + static final TypeLiteral> CS_STRING = new TypeLiteral<>() { + }; + + static final TypeLiteral> CS_INTEGER = new TypeLiteral<>() { + }; +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/Types.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/Types.java new file mode 100644 index 00000000..1367417c --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/Types.java @@ -0,0 +1,15 @@ +package io.smallrye.faulttolerance; + +import java.util.concurrent.CompletionStage; + +import jakarta.enterprise.util.TypeLiteral; + +import io.smallrye.mutiny.Uni; + +public class Types { + public static final TypeLiteral> CS_STRING = new TypeLiteral<>() { + }; + + public static final TypeLiteral> UNI_STRING = new TypeLiteral<>() { + }; +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/circuitbreaker/maintenance/CircuitBreakerMaintenanceInteroperabilityTest.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/circuitbreaker/maintenance/CircuitBreakerMaintenanceInteroperabilityTest.java index 9636255a..67f076bb 100644 --- a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/circuitbreaker/maintenance/CircuitBreakerMaintenanceInteroperabilityTest.java +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/circuitbreaker/maintenance/CircuitBreakerMaintenanceInteroperabilityTest.java @@ -16,7 +16,6 @@ import io.smallrye.faulttolerance.api.CircuitBreakerMaintenance; import io.smallrye.faulttolerance.api.CircuitBreakerState; -import io.smallrye.faulttolerance.api.FaultTolerance; import io.smallrye.faulttolerance.util.FaultToleranceBasicTest; import io.smallrye.faulttolerance.util.WithSystemProperty; @@ -33,14 +32,14 @@ public class CircuitBreakerMaintenanceInteroperabilityTest { @BeforeEach public void reset() { - FaultTolerance.circuitBreakerMaintenance().resetAll(); + CircuitBreakerMaintenance.get().resetAll(); helloService.toString(); // force bean instantiation } @Test public void test() { - CircuitBreakerMaintenance cbm = FaultTolerance.circuitBreakerMaintenance(); + CircuitBreakerMaintenance cbm = CircuitBreakerMaintenance.get(); assertThat(cb.currentState("hello")).isEqualTo(CircuitBreakerState.CLOSED); assertThat(cb.currentState("another-hello")).isEqualTo(CircuitBreakerState.CLOSED); diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/circuitbreaker/maintenance/HelloService.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/circuitbreaker/maintenance/HelloService.java index e5d499e9..f51a081f 100644 --- a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/circuitbreaker/maintenance/HelloService.java +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/circuitbreaker/maintenance/HelloService.java @@ -8,7 +8,7 @@ import org.eclipse.microprofile.faulttolerance.CircuitBreaker; import io.smallrye.faulttolerance.api.CircuitBreakerName; -import io.smallrye.faulttolerance.api.FaultTolerance; +import io.smallrye.faulttolerance.api.Guard; @ApplicationScoped public class HelloService { @@ -16,9 +16,10 @@ public class HelloService { static final int THRESHOLD = 5; static final int DELAY = 500; - private final Supplier anotherHello = FaultTolerance.createSupplier(this::anotherHelloImpl) + private final Supplier anotherHello = Guard.create() .withCircuitBreaker().requestVolumeThreshold(THRESHOLD).delay(DELAY, ChronoUnit.MILLIS).name("another-hello").done() - .build(); + .build() + .adaptSupplier(this::anotherHelloImpl, String.class); @CircuitBreaker(requestVolumeThreshold = THRESHOLD, delay = DELAY) @CircuitBreakerName("hello") diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/programmatic/CdiFaultToleranceCopyAsyncTest.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/programmatic/CdiFaultToleranceCopyAsyncTest.java deleted file mode 100644 index 3367012e..00000000 --- a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/programmatic/CdiFaultToleranceCopyAsyncTest.java +++ /dev/null @@ -1,8 +0,0 @@ -package io.smallrye.faulttolerance.programmatic; - -import io.smallrye.faulttolerance.standalone.test.StandaloneFaultToleranceCastAsyncTest; -import io.smallrye.faulttolerance.util.FaultToleranceBasicTest; - -@FaultToleranceBasicTest -public class CdiFaultToleranceCopyAsyncTest extends StandaloneFaultToleranceCastAsyncTest { -} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/programmatic/CdiFaultToleranceCopyTest.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/programmatic/CdiFaultToleranceCopyTest.java deleted file mode 100644 index 00466816..00000000 --- a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/programmatic/CdiFaultToleranceCopyTest.java +++ /dev/null @@ -1,8 +0,0 @@ -package io.smallrye.faulttolerance.programmatic; - -import io.smallrye.faulttolerance.standalone.test.StandaloneFaultToleranceCastTest; -import io.smallrye.faulttolerance.util.FaultToleranceBasicTest; - -@FaultToleranceBasicTest -public class CdiFaultToleranceCopyTest extends StandaloneFaultToleranceCastTest { -} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/programmatic/CdiMetricsTest.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/programmatic/CdiMetricsTest.java index f6fe8c1a..89a1627e 100644 --- a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/programmatic/CdiMetricsTest.java +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/programmatic/CdiMetricsTest.java @@ -9,7 +9,7 @@ import org.eclipse.microprofile.metrics.annotation.RegistryType; import org.junit.jupiter.api.Test; -import io.smallrye.faulttolerance.api.FaultTolerance; +import io.smallrye.faulttolerance.api.TypedGuard; import io.smallrye.faulttolerance.core.metrics.MetricsConstants; import io.smallrye.faulttolerance.core.util.TestException; import io.smallrye.faulttolerance.util.FaultToleranceBasicTest; @@ -21,11 +21,12 @@ public class CdiMetricsTest { @Test public void test(@RegistryType(type = MetricRegistry.Type.BASE) MetricRegistry metrics) throws Exception { - Callable guarded = FaultTolerance.createCallable(this::action) + Callable guarded = TypedGuard.create(String.class) .withDescription(NAME) .withFallback().handler(this::fallback).done() .withRetry().maxRetries(3).done() - .build(); + .build() + .adaptCallable(this::action); assertThat(guarded.call()).isEqualTo("fallback"); diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/programmatic/CdiMetricsTimerTest.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/programmatic/CdiMetricsTimerTest.java index 76b955ca..b34803c9 100644 --- a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/programmatic/CdiMetricsTimerTest.java +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/programmatic/CdiMetricsTimerTest.java @@ -17,7 +17,8 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; -import io.smallrye.faulttolerance.api.FaultTolerance; +import io.smallrye.faulttolerance.Types; +import io.smallrye.faulttolerance.api.TypedGuard; import io.smallrye.faulttolerance.core.metrics.MetricsConstants; import io.smallrye.faulttolerance.core.util.barrier.Barrier; import io.smallrye.faulttolerance.util.FaultToleranceBasicTest; @@ -34,11 +35,12 @@ public static void setUp() { @Test public void test(@RegistryType(type = MetricRegistry.Type.BASE) MetricRegistry metrics) throws Exception { - Callable> guarded = FaultTolerance.createAsyncCallable(this::action) + Callable> guarded = TypedGuard.create(Types.CS_STRING) .withThreadOffload(true) .withTimeout().duration(1, ChronoUnit.MINUTES).done() .withFallback().handler(this::fallback).done() - .build(); + .build() + .adaptCallable(this::action); CompletableFuture future = guarded.call().toCompletableFuture(); diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/programmatic/CdiSkipFaultToleranceTest.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/programmatic/CdiSkipFaultToleranceTest.java index 6b47c377..c06bfe0b 100644 --- a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/programmatic/CdiSkipFaultToleranceTest.java +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/programmatic/CdiSkipFaultToleranceTest.java @@ -7,7 +7,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import io.smallrye.faulttolerance.api.FaultTolerance; +import io.smallrye.faulttolerance.api.TypedGuard; import io.smallrye.faulttolerance.core.util.TestException; import io.smallrye.faulttolerance.util.FaultToleranceBasicTest; import io.smallrye.faulttolerance.util.WithSystemProperty; @@ -26,10 +26,11 @@ public void setUp() { @Test public void skipFaultTolerance() throws Exception { - Callable guarded = FaultTolerance.createCallable(this::action) + Callable guarded = TypedGuard.create(String.class) .withRetry().maxRetries(3).done() .withFallback().handler(this::fallback).done() - .build(); + .build() + .adaptCallable(this::action); assertThat(guarded.call()).isEqualTo("fallback"); assertThat(counter).isEqualTo(1); // 1 initial invocation, no retries diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/programmatic/CdiUntypedAsyncGuardTest.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/programmatic/CdiUntypedAsyncGuardTest.java new file mode 100644 index 00000000..764fd011 --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/programmatic/CdiUntypedAsyncGuardTest.java @@ -0,0 +1,8 @@ +package io.smallrye.faulttolerance.programmatic; + +import io.smallrye.faulttolerance.standalone.test.StandaloneUntypedAsyncGuardTest; +import io.smallrye.faulttolerance.util.FaultToleranceBasicTest; + +@FaultToleranceBasicTest +public class CdiUntypedAsyncGuardTest extends StandaloneUntypedAsyncGuardTest { +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/programmatic/CdiUntypedGuardTest.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/programmatic/CdiUntypedGuardTest.java new file mode 100644 index 00000000..aae630d9 --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/programmatic/CdiUntypedGuardTest.java @@ -0,0 +1,8 @@ +package io.smallrye.faulttolerance.programmatic; + +import io.smallrye.faulttolerance.standalone.test.StandaloneUntypedGuardTest; +import io.smallrye.faulttolerance.util.FaultToleranceBasicTest; + +@FaultToleranceBasicTest +public class CdiUntypedGuardTest extends StandaloneUntypedGuardTest { +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/completionstage/MyFaultTolerance.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/completionstage/MyFaultTolerance.java index 267cbffc..e1534307 100644 --- a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/completionstage/MyFaultTolerance.java +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/completionstage/MyFaultTolerance.java @@ -8,13 +8,14 @@ import jakarta.enterprise.inject.Produces; import io.smallrye.common.annotation.Identifier; -import io.smallrye.faulttolerance.api.FaultTolerance; +import io.smallrye.faulttolerance.Types; +import io.smallrye.faulttolerance.api.TypedGuard; @ApplicationScoped public class MyFaultTolerance { @Produces @Identifier("my-fault-tolerance") - public static final FaultTolerance> FT = FaultTolerance. createAsync() + public static final TypedGuard> GUARD = TypedGuard.create(Types.CS_STRING) .withRetry().maxRetries(2).done() .withFallback().handler(() -> completedFuture("fallback")).done() .build(); diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/completionstage/MyService.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/completionstage/MyService.java index 0501346b..17ffe8bf 100644 --- a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/completionstage/MyService.java +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/completionstage/MyService.java @@ -7,13 +7,13 @@ import jakarta.enterprise.context.ApplicationScoped; -import io.smallrye.faulttolerance.api.ApplyFaultTolerance; +import io.smallrye.faulttolerance.api.ApplyGuard; @ApplicationScoped public class MyService { static final AtomicInteger COUNTER = new AtomicInteger(0); - @ApplyFaultTolerance("my-fault-tolerance") + @ApplyGuard("my-fault-tolerance") public CompletionStage hello() { COUNTER.incrementAndGet(); return failedFuture(new IllegalArgumentException()); diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/completionstage/metrics/MyFaultTolerance.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/completionstage/metrics/MyFaultTolerance.java index 974f9a15..830cb6b9 100644 --- a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/completionstage/metrics/MyFaultTolerance.java +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/completionstage/metrics/MyFaultTolerance.java @@ -8,13 +8,14 @@ import jakarta.enterprise.inject.Produces; import io.smallrye.common.annotation.Identifier; -import io.smallrye.faulttolerance.api.FaultTolerance; +import io.smallrye.faulttolerance.Types; +import io.smallrye.faulttolerance.api.TypedGuard; @ApplicationScoped public class MyFaultTolerance { @Produces @Identifier("my-fault-tolerance") - public static final FaultTolerance> FT = FaultTolerance. createAsync() + public static final TypedGuard> GUARD = TypedGuard.create(Types.CS_STRING) .withRetry().maxRetries(2).done() .withFallback().handler(() -> completedFuture("fallback")).done() .build(); diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/completionstage/metrics/MyService.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/completionstage/metrics/MyService.java index bb9b5998..a4009839 100644 --- a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/completionstage/metrics/MyService.java +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/completionstage/metrics/MyService.java @@ -6,16 +6,16 @@ import jakarta.enterprise.context.ApplicationScoped; -import io.smallrye.faulttolerance.api.ApplyFaultTolerance; +import io.smallrye.faulttolerance.api.ApplyGuard; @ApplicationScoped public class MyService { - @ApplyFaultTolerance("my-fault-tolerance") + @ApplyGuard("my-fault-tolerance") public CompletionStage first() { return failedFuture(new IllegalArgumentException()); } - @ApplyFaultTolerance("my-fault-tolerance") + @ApplyGuard("my-fault-tolerance") public CompletionStage second() { return failedFuture(new IllegalStateException()); } diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/uni/MyFaultTolerance.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/uni/MyFaultTolerance.java index 0c57fa64..9f21c359 100644 --- a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/uni/MyFaultTolerance.java +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/uni/MyFaultTolerance.java @@ -4,15 +4,15 @@ import jakarta.enterprise.inject.Produces; import io.smallrye.common.annotation.Identifier; -import io.smallrye.faulttolerance.api.FaultTolerance; -import io.smallrye.faulttolerance.mutiny.api.MutinyFaultTolerance; +import io.smallrye.faulttolerance.Types; +import io.smallrye.faulttolerance.api.TypedGuard; import io.smallrye.mutiny.Uni; @ApplicationScoped public class MyFaultTolerance { @Produces @Identifier("my-fault-tolerance") - public static final FaultTolerance> FT = MutinyFaultTolerance. create() + public static final TypedGuard> GUARD = TypedGuard.create(Types.UNI_STRING) .withRetry().maxRetries(2).done() .withFallback().handler(() -> Uni.createFrom().item("fallback")).done() .build(); diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/uni/MyService.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/uni/MyService.java index 35fc8c30..24bb8114 100644 --- a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/uni/MyService.java +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/uni/MyService.java @@ -4,14 +4,14 @@ import jakarta.enterprise.context.ApplicationScoped; -import io.smallrye.faulttolerance.api.ApplyFaultTolerance; +import io.smallrye.faulttolerance.api.ApplyGuard; import io.smallrye.mutiny.Uni; @ApplicationScoped public class MyService { static final AtomicInteger COUNTER = new AtomicInteger(0); - @ApplyFaultTolerance("my-fault-tolerance") + @ApplyGuard("my-fault-tolerance") public Uni hello() { COUNTER.incrementAndGet(); return Uni.createFrom().failure(new IllegalArgumentException()); diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/uni/metrics/MyFaultTolerance.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/uni/metrics/MyFaultTolerance.java index da79627b..15a3f795 100644 --- a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/uni/metrics/MyFaultTolerance.java +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/uni/metrics/MyFaultTolerance.java @@ -4,15 +4,15 @@ import jakarta.enterprise.inject.Produces; import io.smallrye.common.annotation.Identifier; -import io.smallrye.faulttolerance.api.FaultTolerance; -import io.smallrye.faulttolerance.mutiny.api.MutinyFaultTolerance; +import io.smallrye.faulttolerance.Types; +import io.smallrye.faulttolerance.api.TypedGuard; import io.smallrye.mutiny.Uni; @ApplicationScoped public class MyFaultTolerance { @Produces @Identifier("my-fault-tolerance") - public static final FaultTolerance> FT = MutinyFaultTolerance. create() + public static final TypedGuard> GUARD = TypedGuard.create(Types.UNI_STRING) .withRetry().maxRetries(2).done() .withFallback().handler(() -> Uni.createFrom().item("fallback")).done() .build(); diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/uni/metrics/MyService.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/uni/metrics/MyService.java index d8ccaa7b..cfbd15eb 100644 --- a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/uni/metrics/MyService.java +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/uni/metrics/MyService.java @@ -2,17 +2,17 @@ import jakarta.enterprise.context.ApplicationScoped; -import io.smallrye.faulttolerance.api.ApplyFaultTolerance; +import io.smallrye.faulttolerance.api.ApplyGuard; import io.smallrye.mutiny.Uni; @ApplicationScoped public class MyService { - @ApplyFaultTolerance("my-fault-tolerance") + @ApplyGuard("my-fault-tolerance") public Uni first() { return Uni.createFrom().failure(new IllegalArgumentException()); } - @ApplyFaultTolerance("my-fault-tolerance") + @ApplyGuard("my-fault-tolerance") public Uni second() { return Uni.createFrom().failure(new IllegalStateException()); } diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/errors/MultipleGuardsWithSameIdentifierProducer.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/errors/MultipleGuardsWithSameIdentifierProducer.java new file mode 100644 index 00000000..077a8c8c --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/errors/MultipleGuardsWithSameIdentifierProducer.java @@ -0,0 +1,19 @@ +package io.smallrye.faulttolerance.reuse.errors; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.inject.Produces; + +import io.smallrye.common.annotation.Identifier; +import io.smallrye.faulttolerance.api.Guard; +import io.smallrye.faulttolerance.api.TypedGuard; + +@ApplicationScoped +public class MultipleGuardsWithSameIdentifierProducer { + @Produces + @Identifier("foobar") + public static Guard GUARD = Guard.create().build(); + + @Produces + @Identifier("foobar") + public static TypedGuard TYPED_GUARD = TypedGuard.create(String.class).build(); +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/errors/MultipleGuardsWithSameIdentifierTest.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/errors/MultipleGuardsWithSameIdentifierTest.java new file mode 100644 index 00000000..b7b3991e --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/errors/MultipleGuardsWithSameIdentifierTest.java @@ -0,0 +1,16 @@ +package io.smallrye.faulttolerance.reuse.errors; + +import jakarta.enterprise.inject.spi.DefinitionException; + +import org.junit.jupiter.api.Test; + +import io.smallrye.faulttolerance.util.ExpectedDeploymentException; +import io.smallrye.faulttolerance.util.FaultToleranceBasicTest; + +@FaultToleranceBasicTest +@ExpectedDeploymentException(DefinitionException.class) +public class MultipleGuardsWithSameIdentifierTest { + @Test + public void test(MultipleGuardsWithSameIdentifierProducer ignored) { + } +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mismatch/async/vs/async/MyFaultTolerance.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mismatch/async/vs/async/MyFaultTolerance.java deleted file mode 100644 index aac337b5..00000000 --- a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mismatch/async/vs/async/MyFaultTolerance.java +++ /dev/null @@ -1,16 +0,0 @@ -package io.smallrye.faulttolerance.reuse.mismatch.async.vs.async; - -import jakarta.enterprise.context.ApplicationScoped; -import jakarta.enterprise.inject.Produces; - -import io.smallrye.common.annotation.Identifier; -import io.smallrye.faulttolerance.api.FaultTolerance; -import io.smallrye.faulttolerance.mutiny.api.MutinyFaultTolerance; -import io.smallrye.mutiny.Uni; - -@ApplicationScoped -public class MyFaultTolerance { - @Produces - @Identifier("my-fault-tolerance") - public static final FaultTolerance> FT = MutinyFaultTolerance. create().build(); -} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mismatch/async/vs/async/MyService.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mismatch/async/vs/async/MyService.java deleted file mode 100644 index a58fe266..00000000 --- a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mismatch/async/vs/async/MyService.java +++ /dev/null @@ -1,15 +0,0 @@ -package io.smallrye.faulttolerance.reuse.mismatch.async.vs.async; - -import java.util.concurrent.CompletionStage; - -import jakarta.enterprise.context.ApplicationScoped; - -import io.smallrye.faulttolerance.api.ApplyFaultTolerance; - -@ApplicationScoped -public class MyService { - @ApplyFaultTolerance("my-fault-tolerance") - public CompletionStage hello() { - return null; - } -} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mismatch/async/vs/async/ReuseMismatchAsyncVsAsyncTest.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mismatch/async/vs/async/ReuseMismatchAsyncVsAsyncTest.java deleted file mode 100644 index 0d753b73..00000000 --- a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mismatch/async/vs/async/ReuseMismatchAsyncVsAsyncTest.java +++ /dev/null @@ -1,23 +0,0 @@ -package io.smallrye.faulttolerance.reuse.mismatch.async.vs.async; - -import static org.assertj.core.api.Assertions.assertThatCode; - -import java.util.concurrent.ExecutionException; - -import org.eclipse.microprofile.faulttolerance.exceptions.FaultToleranceException; -import org.jboss.weld.junit5.auto.AddBeanClasses; -import org.junit.jupiter.api.Test; - -import io.smallrye.faulttolerance.util.FaultToleranceBasicTest; - -@FaultToleranceBasicTest -@AddBeanClasses(MyFaultTolerance.class) -public class ReuseMismatchAsyncVsAsyncTest { - @Test - public void test(MyService service) throws ExecutionException, InterruptedException { - assertThatCode(service::hello) - .isExactlyInstanceOf(FaultToleranceException.class) - .hasMessageContaining( - "Configured fault tolerance 'my-fault-tolerance' expects the operation to return Uni, but it returns CompletionStage"); - } -} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mismatch/async/vs/sync/MyFaultTolerance.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mismatch/async/vs/sync/MyFaultTolerance.java deleted file mode 100644 index 88bc202e..00000000 --- a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mismatch/async/vs/sync/MyFaultTolerance.java +++ /dev/null @@ -1,16 +0,0 @@ -package io.smallrye.faulttolerance.reuse.mismatch.async.vs.sync; - -import java.util.concurrent.CompletionStage; - -import jakarta.enterprise.context.ApplicationScoped; -import jakarta.enterprise.inject.Produces; - -import io.smallrye.common.annotation.Identifier; -import io.smallrye.faulttolerance.api.FaultTolerance; - -@ApplicationScoped -public class MyFaultTolerance { - @Produces - @Identifier("my-fault-tolerance") - public static final FaultTolerance> FT = FaultTolerance. createAsync().build(); -} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mismatch/async/vs/sync/MyService.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mismatch/async/vs/sync/MyService.java deleted file mode 100644 index 2f078e74..00000000 --- a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mismatch/async/vs/sync/MyService.java +++ /dev/null @@ -1,13 +0,0 @@ -package io.smallrye.faulttolerance.reuse.mismatch.async.vs.sync; - -import jakarta.enterprise.context.ApplicationScoped; - -import io.smallrye.faulttolerance.api.ApplyFaultTolerance; - -@ApplicationScoped -public class MyService { - @ApplyFaultTolerance("my-fault-tolerance") - public String hello() { - return null; - } -} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mismatch/async/vs/sync/ReuseMismatchAsyncVsSyncTest.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mismatch/async/vs/sync/ReuseMismatchAsyncVsSyncTest.java deleted file mode 100644 index 832f51df..00000000 --- a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mismatch/async/vs/sync/ReuseMismatchAsyncVsSyncTest.java +++ /dev/null @@ -1,21 +0,0 @@ -package io.smallrye.faulttolerance.reuse.mismatch.async.vs.sync; - -import static org.assertj.core.api.Assertions.assertThatCode; - -import org.eclipse.microprofile.faulttolerance.exceptions.FaultToleranceException; -import org.jboss.weld.junit5.auto.AddBeanClasses; -import org.junit.jupiter.api.Test; - -import io.smallrye.faulttolerance.util.FaultToleranceBasicTest; - -@FaultToleranceBasicTest -@AddBeanClasses(MyFaultTolerance.class) -public class ReuseMismatchAsyncVsSyncTest { - @Test - public void test(MyService service) { - assertThatCode(service::hello) - .isExactlyInstanceOf(FaultToleranceException.class) - .hasMessageContaining( - "Configured fault tolerance 'my-fault-tolerance' expects the operation to return CompletionStage, but the operation is synchronous"); - } -} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mismatch/sync/vs/async/MyService.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mismatch/sync/vs/async/MyService.java deleted file mode 100644 index c84e5f34..00000000 --- a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mismatch/sync/vs/async/MyService.java +++ /dev/null @@ -1,15 +0,0 @@ -package io.smallrye.faulttolerance.reuse.mismatch.sync.vs.async; - -import java.util.concurrent.CompletionStage; - -import jakarta.enterprise.context.ApplicationScoped; - -import io.smallrye.faulttolerance.api.ApplyFaultTolerance; - -@ApplicationScoped -public class MyService { - @ApplyFaultTolerance("my-fault-tolerance") - public CompletionStage hello() { - return null; - } -} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mismatch/sync/vs/async/ReuseMismatchSyncVsAsyncTest.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mismatch/sync/vs/async/ReuseMismatchSyncVsAsyncTest.java deleted file mode 100644 index df13beb5..00000000 --- a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mismatch/sync/vs/async/ReuseMismatchSyncVsAsyncTest.java +++ /dev/null @@ -1,21 +0,0 @@ -package io.smallrye.faulttolerance.reuse.mismatch.sync.vs.async; - -import static org.assertj.core.api.Assertions.assertThatCode; - -import org.eclipse.microprofile.faulttolerance.exceptions.FaultToleranceException; -import org.jboss.weld.junit5.auto.AddBeanClasses; -import org.junit.jupiter.api.Test; - -import io.smallrye.faulttolerance.util.FaultToleranceBasicTest; - -@FaultToleranceBasicTest -@AddBeanClasses(MyFaultTolerance.class) -public class ReuseMismatchSyncVsAsyncTest { - @Test - public void test(MyService service) { - assertThatCode(service::hello) - .isExactlyInstanceOf(FaultToleranceException.class) - .hasMessageContaining( - "Configured fault tolerance 'my-fault-tolerance' expects the operation to be synchronous, but it returns CompletionStage"); - } -} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/missing/MyService.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/missing/MyService.java index fd741d02..9fe4d331 100644 --- a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/missing/MyService.java +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/missing/MyService.java @@ -2,11 +2,11 @@ import jakarta.enterprise.context.ApplicationScoped; -import io.smallrye.faulttolerance.api.ApplyFaultTolerance; +import io.smallrye.faulttolerance.api.ApplyGuard; @ApplicationScoped public class MyService { - @ApplyFaultTolerance("my-fault-tolerance") + @ApplyGuard("my-fault-tolerance") public String hello() { return null; } diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/missing/ReuseMissingTest.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/missing/ReuseMissingTest.java index 966b0686..3067ddb4 100644 --- a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/missing/ReuseMissingTest.java +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/missing/ReuseMissingTest.java @@ -13,7 +13,7 @@ public class ReuseMissingTest { public void test(MyService service) { assertThatCode(service::hello) .isExactlyInstanceOf(FaultToleranceException.class) - .hasMessageContaining( - "Can't resolve a bean of type io.smallrye.faulttolerance.api.FaultTolerance with qualifier @io.smallrye.common.annotation.Identifier(\"my-fault-tolerance\")"); + .hasMessageContaining("Can't resolve a bean of type") + .hasMessageContaining("with qualifier @io.smallrye.common.annotation.Identifier(\"my-fault-tolerance\")"); } } diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/all/MixedReuseAllTest.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/all/MixedReuseAllTest.java new file mode 100644 index 00000000..1c4c1852 --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/all/MixedReuseAllTest.java @@ -0,0 +1,26 @@ +package io.smallrye.faulttolerance.reuse.mixed.all; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.concurrent.ExecutionException; + +import org.jboss.weld.junit5.auto.AddBeanClasses; +import org.junit.jupiter.api.Test; + +import io.smallrye.faulttolerance.util.FaultToleranceBasicTest; + +@FaultToleranceBasicTest +@AddBeanClasses(MyFaultTolerance.class) +public class MixedReuseAllTest { + @Test + public void test(MyService service) throws ExecutionException, InterruptedException { + assertThat(service.hello()).isEqualTo("hello"); + assertThat(MyService.STRING_COUNTER).hasValue(4); + + assertThat(service.theAnswer().toCompletableFuture().get()).isEqualTo(42); + assertThat(MyService.INT_COUNTER).hasValue(4); + + assertThat(service.badNumber().subscribeAsCompletionStage().toCompletableFuture().get()).isEqualTo(13L); + assertThat(MyService.LONG_COUNTER).hasValue(4); + } +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mismatch/sync/vs/async/MyFaultTolerance.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/all/MyFaultTolerance.java similarity index 53% rename from testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mismatch/sync/vs/async/MyFaultTolerance.java rename to testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/all/MyFaultTolerance.java index d5d1d1fe..b6260fcb 100644 --- a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mismatch/sync/vs/async/MyFaultTolerance.java +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/all/MyFaultTolerance.java @@ -1,14 +1,16 @@ -package io.smallrye.faulttolerance.reuse.mismatch.sync.vs.async; +package io.smallrye.faulttolerance.reuse.mixed.all; import jakarta.enterprise.context.ApplicationScoped; import jakarta.enterprise.inject.Produces; import io.smallrye.common.annotation.Identifier; -import io.smallrye.faulttolerance.api.FaultTolerance; +import io.smallrye.faulttolerance.api.Guard; @ApplicationScoped public class MyFaultTolerance { @Produces @Identifier("my-fault-tolerance") - public static final FaultTolerance FT = FaultTolerance. create().build(); + public static final Guard GUARD = Guard.create() + .withRetry().maxRetries(5).done() + .build(); } diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/all/MyService.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/all/MyService.java new file mode 100644 index 00000000..53918764 --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/all/MyService.java @@ -0,0 +1,43 @@ +package io.smallrye.faulttolerance.reuse.mixed.all; + +import static java.util.concurrent.CompletableFuture.completedFuture; +import static java.util.concurrent.CompletableFuture.failedFuture; + +import java.util.concurrent.CompletionStage; +import java.util.concurrent.atomic.AtomicInteger; + +import jakarta.enterprise.context.ApplicationScoped; + +import io.smallrye.faulttolerance.api.ApplyGuard; +import io.smallrye.mutiny.Uni; + +@ApplicationScoped +public class MyService { + static final AtomicInteger STRING_COUNTER = new AtomicInteger(0); + static final AtomicInteger INT_COUNTER = new AtomicInteger(0); + static final AtomicInteger LONG_COUNTER = new AtomicInteger(0); + + @ApplyGuard("my-fault-tolerance") + public String hello() { + if (STRING_COUNTER.incrementAndGet() > 3) { + return "hello"; + } + throw new IllegalArgumentException(); + } + + @ApplyGuard("my-fault-tolerance") + public CompletionStage theAnswer() { + if (INT_COUNTER.incrementAndGet() > 3) { + return completedFuture(42); + } + return failedFuture(new IllegalArgumentException()); + } + + @ApplyGuard("my-fault-tolerance") + public Uni badNumber() { + if (LONG_COUNTER.incrementAndGet() > 3) { + return Uni.createFrom().item(13L); + } + return Uni.createFrom().failure(new IllegalArgumentException()); + } +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/all/metrics/MixedReuseAllMetricsTest.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/all/metrics/MixedReuseAllMetricsTest.java new file mode 100644 index 00000000..a9be0df2 --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/all/metrics/MixedReuseAllMetricsTest.java @@ -0,0 +1,90 @@ +package io.smallrye.faulttolerance.reuse.mixed.all.metrics; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; + +import java.util.concurrent.ExecutionException; + +import org.eclipse.microprofile.metrics.MetricRegistry; +import org.eclipse.microprofile.metrics.Tag; +import org.eclipse.microprofile.metrics.annotation.RegistryType; +import org.jboss.weld.junit5.auto.AddBeanClasses; +import org.junit.jupiter.api.Test; + +import io.smallrye.faulttolerance.util.FaultToleranceBasicTest; + +@FaultToleranceBasicTest +@AddBeanClasses(MyFaultTolerance.class) +public class MixedReuseAllMetricsTest { + @Test + public void test(MyService service, @RegistryType(type = MetricRegistry.Type.BASE) MetricRegistry metrics) + throws ExecutionException, InterruptedException { + // 1 + assertThat(service.first()).isEqualTo("hello"); + // 2 + assertThat(service.second().toCompletableFuture().get()).isEqualTo(42); + service.resetSecondCounter(); + assertThat(service.second().toCompletableFuture().get()).isEqualTo(42); + // 3 + assertThatCode(service.third().subscribeAsCompletionStage().toCompletableFuture()::get) + .isExactlyInstanceOf(ExecutionException.class) + .hasCauseExactlyInstanceOf(IllegalArgumentException.class); + assertThatCode(service.third().subscribeAsCompletionStage().toCompletableFuture()::get) + .isExactlyInstanceOf(ExecutionException.class) + .hasCauseExactlyInstanceOf(IllegalArgumentException.class); + assertThatCode(service.third().subscribeAsCompletionStage().toCompletableFuture()::get) + .isExactlyInstanceOf(ExecutionException.class) + .hasCauseExactlyInstanceOf(IllegalArgumentException.class); + + // first + + assertThat(metrics.counter("ft.invocations.total", + new Tag("method", "io.smallrye.faulttolerance.reuse.mixed.all.metrics.MyService.first"), + new Tag("result", "valueReturned"), + new Tag("fallback", "notDefined")) + .getCount()).isEqualTo(1); + + assertThat(metrics.counter("ft.retry.retries.total", + new Tag("method", "io.smallrye.faulttolerance.reuse.mixed.all.metrics.MyService.first")) + .getCount()).isEqualTo(0); + assertThat(metrics.counter("ft.retry.calls.total", + new Tag("method", "io.smallrye.faulttolerance.reuse.mixed.all.metrics.MyService.first"), + new Tag("retried", "false"), + new Tag("retryResult", "valueReturned")) + .getCount()).isEqualTo(1); + + // second + + assertThat(metrics.counter("ft.invocations.total", + new Tag("method", "io.smallrye.faulttolerance.reuse.mixed.all.metrics.MyService.second"), + new Tag("result", "valueReturned"), + new Tag("fallback", "notDefined")) + .getCount()).isEqualTo(2); + + assertThat(metrics.counter("ft.retry.retries.total", + new Tag("method", "io.smallrye.faulttolerance.reuse.mixed.all.metrics.MyService.second")) + .getCount()).isEqualTo(6); + assertThat(metrics.counter("ft.retry.calls.total", + new Tag("method", "io.smallrye.faulttolerance.reuse.mixed.all.metrics.MyService.second"), + new Tag("retried", "true"), + new Tag("retryResult", "valueReturned")) + .getCount()).isEqualTo(2); + + // third + + assertThat(metrics.counter("ft.invocations.total", + new Tag("method", "io.smallrye.faulttolerance.reuse.mixed.all.metrics.MyService.third"), + new Tag("result", "exceptionThrown"), + new Tag("fallback", "notDefined")) + .getCount()).isEqualTo(3); + + assertThat(metrics.counter("ft.retry.retries.total", + new Tag("method", "io.smallrye.faulttolerance.reuse.mixed.all.metrics.MyService.third")) + .getCount()).isEqualTo(15); + assertThat(metrics.counter("ft.retry.calls.total", + new Tag("method", "io.smallrye.faulttolerance.reuse.mixed.all.metrics.MyService.third"), + new Tag("retried", "true"), + new Tag("retryResult", "maxRetriesReached")) + .getCount()).isEqualTo(3); + } +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/all/metrics/MyFaultTolerance.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/all/metrics/MyFaultTolerance.java new file mode 100644 index 00000000..03032fb8 --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/all/metrics/MyFaultTolerance.java @@ -0,0 +1,16 @@ +package io.smallrye.faulttolerance.reuse.mixed.all.metrics; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.inject.Produces; + +import io.smallrye.common.annotation.Identifier; +import io.smallrye.faulttolerance.api.Guard; + +@ApplicationScoped +public class MyFaultTolerance { + @Produces + @Identifier("my-fault-tolerance") + public static final Guard GUARD = Guard.create() + .withRetry().maxRetries(5).done() + .build(); +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/all/metrics/MyService.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/all/metrics/MyService.java new file mode 100644 index 00000000..d0a7779a --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/all/metrics/MyService.java @@ -0,0 +1,39 @@ +package io.smallrye.faulttolerance.reuse.mixed.all.metrics; + +import static java.util.concurrent.CompletableFuture.completedFuture; +import static java.util.concurrent.CompletableFuture.failedFuture; + +import java.util.concurrent.CompletionStage; +import java.util.concurrent.atomic.AtomicInteger; + +import jakarta.enterprise.context.ApplicationScoped; + +import io.smallrye.faulttolerance.api.ApplyGuard; +import io.smallrye.mutiny.Uni; + +@ApplicationScoped +public class MyService { + private static final AtomicInteger counter = new AtomicInteger(0); + + @ApplyGuard("my-fault-tolerance") + public String first() { + return "hello"; + } + + @ApplyGuard("my-fault-tolerance") + public CompletionStage second() { + if (counter.incrementAndGet() > 3) { + return completedFuture(42); + } + return failedFuture(new IllegalArgumentException()); + } + + public void resetSecondCounter() { + counter.set(0); + } + + @ApplyGuard("my-fault-tolerance") + public Uni third() { + return Uni.createFrom().failure(new IllegalArgumentException()); + } +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/async/both/MixedReuseAsyncBothTest.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/async/both/MixedReuseAsyncBothTest.java new file mode 100644 index 00000000..6fb5a1d7 --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/async/both/MixedReuseAsyncBothTest.java @@ -0,0 +1,23 @@ +package io.smallrye.faulttolerance.reuse.mixed.async.both; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.concurrent.ExecutionException; + +import org.jboss.weld.junit5.auto.AddBeanClasses; +import org.junit.jupiter.api.Test; + +import io.smallrye.faulttolerance.util.FaultToleranceBasicTest; + +@FaultToleranceBasicTest +@AddBeanClasses(MyFaultTolerance.class) +public class MixedReuseAsyncBothTest { + @Test + public void test(MyService service) throws ExecutionException, InterruptedException { + assertThat(service.hello().toCompletableFuture().get()).isEqualTo("hello"); + assertThat(MyService.STRING_COUNTER).hasValue(4); + + assertThat(service.theAnswer().subscribeAsCompletionStage().toCompletableFuture().get()).isEqualTo(42); + assertThat(MyService.INT_COUNTER).hasValue(4); + } +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/async/both/MyFaultTolerance.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/async/both/MyFaultTolerance.java new file mode 100644 index 00000000..77d6c0a4 --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/async/both/MyFaultTolerance.java @@ -0,0 +1,16 @@ +package io.smallrye.faulttolerance.reuse.mixed.async.both; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.inject.Produces; + +import io.smallrye.common.annotation.Identifier; +import io.smallrye.faulttolerance.api.Guard; + +@ApplicationScoped +public class MyFaultTolerance { + @Produces + @Identifier("my-fault-tolerance") + public static final Guard GUARD = Guard.create() + .withRetry().maxRetries(5).done() + .build(); +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/async/both/MyService.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/async/both/MyService.java new file mode 100644 index 00000000..a8ce7da9 --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/async/both/MyService.java @@ -0,0 +1,34 @@ +package io.smallrye.faulttolerance.reuse.mixed.async.both; + +import static java.util.concurrent.CompletableFuture.completedFuture; +import static java.util.concurrent.CompletableFuture.failedFuture; + +import java.util.concurrent.CompletionStage; +import java.util.concurrent.atomic.AtomicInteger; + +import jakarta.enterprise.context.ApplicationScoped; + +import io.smallrye.faulttolerance.api.ApplyGuard; +import io.smallrye.mutiny.Uni; + +@ApplicationScoped +public class MyService { + static final AtomicInteger STRING_COUNTER = new AtomicInteger(0); + static final AtomicInteger INT_COUNTER = new AtomicInteger(0); + + @ApplyGuard("my-fault-tolerance") + public CompletionStage hello() { + if (STRING_COUNTER.incrementAndGet() > 3) { + return completedFuture("hello"); + } + return failedFuture(new IllegalArgumentException()); + } + + @ApplyGuard("my-fault-tolerance") + public Uni theAnswer() { + if (INT_COUNTER.incrementAndGet() > 3) { + return Uni.createFrom().item(42); + } + return Uni.createFrom().failure(new IllegalArgumentException()); + } +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/async/completionstage/MyFaultTolerance.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/async/completionstage/MyFaultTolerance.java index 89a7dc6b..39680b09 100644 --- a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/async/completionstage/MyFaultTolerance.java +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/async/completionstage/MyFaultTolerance.java @@ -1,19 +1,16 @@ package io.smallrye.faulttolerance.reuse.mixed.async.completionstage; -import java.util.concurrent.CompletionStage; - import jakarta.enterprise.context.ApplicationScoped; import jakarta.enterprise.inject.Produces; import io.smallrye.common.annotation.Identifier; -import io.smallrye.faulttolerance.api.FaultTolerance; +import io.smallrye.faulttolerance.api.Guard; @ApplicationScoped public class MyFaultTolerance { - // can't define fallback, that's intrinsically typed @Produces @Identifier("my-fault-tolerance") - public static final FaultTolerance> FT = FaultTolerance.createAsync() + public static final Guard GUARD = Guard.create() .withRetry().maxRetries(5).done() .build(); } diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/async/completionstage/MyService.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/async/completionstage/MyService.java index 7cb147c7..620b5986 100644 --- a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/async/completionstage/MyService.java +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/async/completionstage/MyService.java @@ -8,14 +8,14 @@ import jakarta.enterprise.context.ApplicationScoped; -import io.smallrye.faulttolerance.api.ApplyFaultTolerance; +import io.smallrye.faulttolerance.api.ApplyGuard; @ApplicationScoped public class MyService { static final AtomicInteger STRING_COUNTER = new AtomicInteger(0); static final AtomicInteger INT_COUNTER = new AtomicInteger(0); - @ApplyFaultTolerance("my-fault-tolerance") + @ApplyGuard("my-fault-tolerance") public CompletionStage hello() { if (STRING_COUNTER.incrementAndGet() > 3) { return completedFuture("hello"); @@ -23,7 +23,7 @@ public CompletionStage hello() { return failedFuture(new IllegalArgumentException()); } - @ApplyFaultTolerance("my-fault-tolerance") + @ApplyGuard("my-fault-tolerance") public CompletionStage theAnswer() { if (INT_COUNTER.incrementAndGet() > 3) { return completedFuture(42); diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/async/uni/MyFaultTolerance.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/async/uni/MyFaultTolerance.java index 34205781..a192e3c3 100644 --- a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/async/uni/MyFaultTolerance.java +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/async/uni/MyFaultTolerance.java @@ -4,16 +4,13 @@ import jakarta.enterprise.inject.Produces; import io.smallrye.common.annotation.Identifier; -import io.smallrye.faulttolerance.api.FaultTolerance; -import io.smallrye.faulttolerance.mutiny.api.MutinyFaultTolerance; -import io.smallrye.mutiny.Uni; +import io.smallrye.faulttolerance.api.Guard; @ApplicationScoped public class MyFaultTolerance { - // can't define fallback, that's intrinsically typed @Produces @Identifier("my-fault-tolerance") - public static final FaultTolerance> FT = MutinyFaultTolerance.create() + public static final Guard GUARD = Guard.create() .withRetry().maxRetries(5).done() .build(); } diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/async/uni/MyService.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/async/uni/MyService.java index cf431d42..13623698 100644 --- a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/async/uni/MyService.java +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/async/uni/MyService.java @@ -4,7 +4,7 @@ import jakarta.enterprise.context.ApplicationScoped; -import io.smallrye.faulttolerance.api.ApplyFaultTolerance; +import io.smallrye.faulttolerance.api.ApplyGuard; import io.smallrye.mutiny.Uni; @ApplicationScoped @@ -12,7 +12,7 @@ public class MyService { static final AtomicInteger STRING_COUNTER = new AtomicInteger(0); static final AtomicInteger INT_COUNTER = new AtomicInteger(0); - @ApplyFaultTolerance("my-fault-tolerance") + @ApplyGuard("my-fault-tolerance") public Uni hello() { if (STRING_COUNTER.incrementAndGet() > 3) { return Uni.createFrom().item("hello"); @@ -20,7 +20,7 @@ public Uni hello() { return Uni.createFrom().failure(new IllegalArgumentException()); } - @ApplyFaultTolerance("my-fault-tolerance") + @ApplyGuard("my-fault-tolerance") public Uni theAnswer() { if (INT_COUNTER.incrementAndGet() > 3) { return Uni.createFrom().item(42); diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/sync/MyFaultTolerance.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/sync/MyFaultTolerance.java index c17a66b8..f867c6d0 100644 --- a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/sync/MyFaultTolerance.java +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/sync/MyFaultTolerance.java @@ -4,14 +4,13 @@ import jakarta.enterprise.inject.Produces; import io.smallrye.common.annotation.Identifier; -import io.smallrye.faulttolerance.api.FaultTolerance; +import io.smallrye.faulttolerance.api.Guard; @ApplicationScoped public class MyFaultTolerance { - // can't define fallback, that's intrinsically typed @Produces @Identifier("my-fault-tolerance") - public static final FaultTolerance FT = FaultTolerance.create() + public static final Guard GUARD = Guard.create() .withRetry().maxRetries(5).done() .build(); } diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/sync/MyService.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/sync/MyService.java index b62356b1..a557588d 100644 --- a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/sync/MyService.java +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/sync/MyService.java @@ -4,14 +4,14 @@ import jakarta.enterprise.context.ApplicationScoped; -import io.smallrye.faulttolerance.api.ApplyFaultTolerance; +import io.smallrye.faulttolerance.api.ApplyGuard; @ApplicationScoped public class MyService { static final AtomicInteger STRING_COUNTER = new AtomicInteger(0); static final AtomicInteger INT_COUNTER = new AtomicInteger(0); - @ApplyFaultTolerance("my-fault-tolerance") + @ApplyGuard("my-fault-tolerance") public String hello() { if (STRING_COUNTER.incrementAndGet() > 3) { return "hello"; @@ -19,7 +19,7 @@ public String hello() { throw new IllegalArgumentException(); } - @ApplyFaultTolerance("my-fault-tolerance") + @ApplyGuard("my-fault-tolerance") public int theAnswer() { if (INT_COUNTER.incrementAndGet() > 3) { return 42; diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/sync/MyFaultTolerance.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/sync/MyFaultTolerance.java index 46cae3b1..23cf2477 100644 --- a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/sync/MyFaultTolerance.java +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/sync/MyFaultTolerance.java @@ -4,13 +4,13 @@ import jakarta.enterprise.inject.Produces; import io.smallrye.common.annotation.Identifier; -import io.smallrye.faulttolerance.api.FaultTolerance; +import io.smallrye.faulttolerance.api.TypedGuard; @ApplicationScoped public class MyFaultTolerance { @Produces @Identifier("my-fault-tolerance") - public static final FaultTolerance FT = FaultTolerance. create() + public static final TypedGuard GUARD = TypedGuard.create(String.class) .withRetry().maxRetries(2).done() .withFallback().handler(() -> "fallback").done() .build(); diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/sync/MyService.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/sync/MyService.java index daf17cdc..315b9709 100644 --- a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/sync/MyService.java +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/sync/MyService.java @@ -4,13 +4,13 @@ import jakarta.enterprise.context.ApplicationScoped; -import io.smallrye.faulttolerance.api.ApplyFaultTolerance; +import io.smallrye.faulttolerance.api.ApplyGuard; @ApplicationScoped public class MyService { static final AtomicInteger COUNTER = new AtomicInteger(0); - @ApplyFaultTolerance("my-fault-tolerance") + @ApplyGuard("my-fault-tolerance") public String hello() { COUNTER.incrementAndGet(); throw new IllegalArgumentException(); diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/sync/metrics/MyFaultTolerance.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/sync/metrics/MyFaultTolerance.java index 2b8c70a4..437ced2b 100644 --- a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/sync/metrics/MyFaultTolerance.java +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/sync/metrics/MyFaultTolerance.java @@ -4,13 +4,13 @@ import jakarta.enterprise.inject.Produces; import io.smallrye.common.annotation.Identifier; -import io.smallrye.faulttolerance.api.FaultTolerance; +import io.smallrye.faulttolerance.api.TypedGuard; @ApplicationScoped public class MyFaultTolerance { @Produces @Identifier("my-fault-tolerance") - public static final FaultTolerance FT = FaultTolerance. create() + public static final TypedGuard GUARD = TypedGuard.create(String.class) .withRetry().maxRetries(2).done() .withFallback().handler(() -> "fallback").done() .build(); diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/sync/metrics/MyService.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/sync/metrics/MyService.java index a960fe83..8b698020 100644 --- a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/sync/metrics/MyService.java +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/sync/metrics/MyService.java @@ -2,16 +2,16 @@ import jakarta.enterprise.context.ApplicationScoped; -import io.smallrye.faulttolerance.api.ApplyFaultTolerance; +import io.smallrye.faulttolerance.api.ApplyGuard; @ApplicationScoped public class MyService { - @ApplyFaultTolerance("my-fault-tolerance") + @ApplyGuard("my-fault-tolerance") public String first() { throw new IllegalArgumentException(); } - @ApplyFaultTolerance("my-fault-tolerance") + @ApplyGuard("my-fault-tolerance") public String second() { throw new IllegalStateException(); } diff --git a/testsuite/basic/src/test/kotlin/io/smallrye/faulttolerance/kotlin/reuse/MyFaultTolerance.kt b/testsuite/basic/src/test/kotlin/io/smallrye/faulttolerance/kotlin/reuse/MyFaultTolerance.kt index 090acb91..7ca6e701 100644 --- a/testsuite/basic/src/test/kotlin/io/smallrye/faulttolerance/kotlin/reuse/MyFaultTolerance.kt +++ b/testsuite/basic/src/test/kotlin/io/smallrye/faulttolerance/kotlin/reuse/MyFaultTolerance.kt @@ -1,17 +1,17 @@ package io.smallrye.faulttolerance.kotlin.reuse import io.smallrye.common.annotation.Identifier -import io.smallrye.faulttolerance.api.FaultTolerance -import java.util.function.Supplier +import io.smallrye.faulttolerance.api.TypedGuard import jakarta.enterprise.context.ApplicationScoped import jakarta.enterprise.inject.Produces +import java.util.function.Supplier @ApplicationScoped object MyFaultTolerance { @Produces @Identifier("my-fault-tolerance") - val FT = FaultTolerance.create() - .withRetry().maxRetries(2).done() - .withFallback().handler(Supplier { "fallback" }).done() - .build() + val GUARD = TypedGuard.create(String::class.java) + .withRetry().maxRetries(2).done() + .withFallback().handler(Supplier { "fallback" }).done() + .build() } diff --git a/testsuite/basic/src/test/kotlin/io/smallrye/faulttolerance/kotlin/reuse/MyService.kt b/testsuite/basic/src/test/kotlin/io/smallrye/faulttolerance/kotlin/reuse/MyService.kt index 88631e1f..06ae88e7 100644 --- a/testsuite/basic/src/test/kotlin/io/smallrye/faulttolerance/kotlin/reuse/MyService.kt +++ b/testsuite/basic/src/test/kotlin/io/smallrye/faulttolerance/kotlin/reuse/MyService.kt @@ -1,9 +1,8 @@ package io.smallrye.faulttolerance.kotlin.reuse -import io.smallrye.faulttolerance.api.ApplyFaultTolerance -import java.lang.IllegalArgumentException -import java.util.concurrent.atomic.AtomicInteger +import io.smallrye.faulttolerance.api.ApplyGuard import jakarta.enterprise.context.ApplicationScoped +import java.util.concurrent.atomic.AtomicInteger @ApplicationScoped open class MyService { @@ -11,7 +10,7 @@ open class MyService { val COUNTER = AtomicInteger(0) } - @ApplyFaultTolerance("my-fault-tolerance") + @ApplyGuard("my-fault-tolerance") open fun hello(): String { COUNTER.incrementAndGet() throw IllegalArgumentException() diff --git a/testsuite/basic/src/test/kotlin/io/smallrye/faulttolerance/kotlin/reuse/metrics/MyFaultTolerance.kt b/testsuite/basic/src/test/kotlin/io/smallrye/faulttolerance/kotlin/reuse/metrics/MyFaultTolerance.kt index dd67af50..501cb7e2 100644 --- a/testsuite/basic/src/test/kotlin/io/smallrye/faulttolerance/kotlin/reuse/metrics/MyFaultTolerance.kt +++ b/testsuite/basic/src/test/kotlin/io/smallrye/faulttolerance/kotlin/reuse/metrics/MyFaultTolerance.kt @@ -1,17 +1,17 @@ package io.smallrye.faulttolerance.kotlin.reuse.metrics import io.smallrye.common.annotation.Identifier -import io.smallrye.faulttolerance.api.FaultTolerance -import java.util.function.Supplier +import io.smallrye.faulttolerance.api.TypedGuard import jakarta.enterprise.context.ApplicationScoped import jakarta.enterprise.inject.Produces +import java.util.function.Supplier @ApplicationScoped object MyFaultTolerance { @Produces @Identifier("my-fault-tolerance") - val FT = FaultTolerance.create() - .withRetry().maxRetries(2).done() - .withFallback().handler(Supplier { "fallback" }).done() - .build() + val GUARD = TypedGuard.create(String::class.java) + .withRetry().maxRetries(2).done() + .withFallback().handler(Supplier { "fallback" }).done() + .build() } diff --git a/testsuite/basic/src/test/kotlin/io/smallrye/faulttolerance/kotlin/reuse/metrics/MyService.kt b/testsuite/basic/src/test/kotlin/io/smallrye/faulttolerance/kotlin/reuse/metrics/MyService.kt index 3db3ff1e..3839e76b 100644 --- a/testsuite/basic/src/test/kotlin/io/smallrye/faulttolerance/kotlin/reuse/metrics/MyService.kt +++ b/testsuite/basic/src/test/kotlin/io/smallrye/faulttolerance/kotlin/reuse/metrics/MyService.kt @@ -1,18 +1,16 @@ package io.smallrye.faulttolerance.kotlin.reuse.metrics -import io.smallrye.faulttolerance.api.ApplyFaultTolerance -import java.lang.IllegalArgumentException -import java.util.concurrent.atomic.AtomicInteger +import io.smallrye.faulttolerance.api.ApplyGuard import jakarta.enterprise.context.ApplicationScoped @ApplicationScoped open class MyService { - @ApplyFaultTolerance("my-fault-tolerance") + @ApplyGuard("my-fault-tolerance") open fun first(): String { throw IllegalArgumentException() } - @ApplyFaultTolerance("my-fault-tolerance") + @ApplyGuard("my-fault-tolerance") open fun second(): String { throw IllegalStateException() }