From 4e8c7c4e66e0a71d0605bc6084b236d3f42b703a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 12 Jan 2022 07:11:34 +0000 Subject: [PATCH 01/68] Bump mutiny from 1.2.0 to 1.3.0 Bumps [mutiny](https://github.com/smallrye/smallrye-mutiny) from 1.2.0 to 1.3.0. - [Release notes](https://github.com/smallrye/smallrye-mutiny/releases) - [Commits](https://github.com/smallrye/smallrye-mutiny/compare/1.2.0...1.3.0) --- updated-dependencies: - dependency-name: io.smallrye.reactive:mutiny dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 6bf685ec..9c647baf 100644 --- a/pom.xml +++ b/pom.xml @@ -58,7 +58,7 @@ 4.1.1 5.8.2 1.5.0 - 1.2.0 + 1.3.0 3.1.3 6.14.3 3.1.SP4 From 42c68ee1b8a92e76d51906dde761045ee2fc5470 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 13 Jan 2022 07:10:03 +0000 Subject: [PATCH 02/68] Bump jboss-logging from 3.4.2.Final to 3.4.3.Final Bumps [jboss-logging](https://github.com/jboss-logging/jboss-logging) from 3.4.2.Final to 3.4.3.Final. - [Release notes](https://github.com/jboss-logging/jboss-logging/releases) - [Commits](https://github.com/jboss-logging/jboss-logging/compare/3.4.2.Final...3.4.3.Final) --- updated-dependencies: - dependency-name: org.jboss.logging:jboss-logging dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 9c647baf..69b70fce 100644 --- a/pom.xml +++ b/pom.xml @@ -35,7 +35,7 @@ 2.0.2 1.2.5 1.13.0 - 3.4.2.Final + 3.4.3.Final 2.2.1.Final 3.0 2.0 From dc649cb2d3e366e445e71f904bc1d009094bbfec Mon Sep 17 00:00:00 2001 From: Ladislav Thon Date: Fri, 14 Jan 2022 17:43:50 +0100 Subject: [PATCH 03/68] make CircuitBreaker/Fallback/Retry inspect the exception cause chain --- doc/modules/ROOT/pages/usage/extra.adoc | 70 +++++++++- .../core/circuit/breaker/CircuitBreaker.java | 14 +- .../CompletionStageCircuitBreaker.java | 6 +- .../fallback/CompletionStageFallback.java | 6 +- .../core/fallback/Fallback.java | 12 +- .../core/retry/CompletionStageRetry.java | 6 +- .../faulttolerance/core/retry/Retry.java | 14 +- .../core/util/ExceptionDecision.java | 72 ++++++++++ .../core/util/SetOfThrowables.java | 21 ++- .../circuit/breaker/CircuitBreakerTest.java | 15 +- .../CompletionStageCircuitBreakerTest.java | 10 +- .../breaker/FutureCircuitBreakerTest.java | 12 +- .../core/composition/Strategies.java | 16 +-- .../fallback/CompletionStageFallbackTest.java | 38 +++--- .../core/fallback/FallbackTest.java | 30 ++-- .../core/fallback/FutureFallbackTest.java | 12 +- .../core/retry/CompletionStageRetryTest.java | 17 ++- .../core/retry/FutureRetryTest.java | 26 ++-- .../faulttolerance/core/retry/RetryTest.java | 129 +++++++++++------- .../core/util/ExceptionDecisionTest.java | 45 ++++++ .../core/util/SetOfThrowablesTest.java | 27 +++- .../FaultToleranceInterceptor.java | 36 +++-- .../faulttolerance/SpecCompatibility.java | 4 + .../causechain/ExpectedOutcomeException.java | 10 ++ .../FallbackWithExceptionCauseChainTest.java | 36 +++++ .../fallback/causechain/MyService.java | 18 +++ 26 files changed, 503 insertions(+), 199 deletions(-) create mode 100644 implementation/core/src/main/java/io/smallrye/faulttolerance/core/util/ExceptionDecision.java create mode 100644 implementation/core/src/test/java/io/smallrye/faulttolerance/core/util/ExceptionDecisionTest.java create mode 100644 testsuite/basic/src/test/java/io/smallrye/faulttolerance/fallback/causechain/ExpectedOutcomeException.java create mode 100644 testsuite/basic/src/test/java/io/smallrye/faulttolerance/fallback/causechain/FallbackWithExceptionCauseChainTest.java create mode 100644 testsuite/basic/src/test/java/io/smallrye/faulttolerance/fallback/causechain/MyService.java diff --git a/doc/modules/ROOT/pages/usage/extra.adoc b/doc/modules/ROOT/pages/usage/extra.adoc index 13b06609..67433f0a 100644 --- a/doc/modules/ROOT/pages/usage/extra.adoc +++ b/doc/modules/ROOT/pages/usage/extra.adoc @@ -290,8 +290,8 @@ All `@Retry` metrics are still present and reflect the altered behavior. == Non-compatible Mode -In addition to the <> annotations, {smallrye-fault-tolerance} offers a mode where method asynchrony is determined solely from the its return type. -This mode is *not compatible* with the {microprofile-fault-tolerance} specification (and doesn't pass 2 tests in the TCK). +{smallrye-fault-tolerance} offers a mode where certain features are improved beyond specification, as described below. +This mode is *not compatible* with the {microprofile-fault-tolerance} specification (and doesn't necessarily pass the entire TCK). This mode is disabled by default. To enable, set the configuration property `smallrye.faulttolerance.mp-compatibility` to `false`. @@ -310,7 +310,10 @@ Note that the non-compatible mode is available since {smallrye-fault-tolerance} Previous versions are always compatible. **** -In this mode, methods that +=== Determining Method Asynchrony from Return Type + +In addition to the <> annotations, in the non-compatible mode, method asynchrony is determined solely from its return type. +That is, methods that * have some fault tolerance annotation (such as `@Retry`), * return `CompletionStage` (or some other <>), @@ -354,6 +357,65 @@ Note that the existing annotations still work without a change, both in compatib That is, if a method (or class) is annotated `@Asynchronous` or `@Blocking`, execution will be offloaded to a thread pool. If a method (or class) is annotated `@NonBlocking`, execution will happen on the original thread (even if `@Asynchronous` is present). -Also note that this mode doesn't affect methods returning `Future`. +Also note that this doesn't affect methods returning `Future`. You still have to annotate them `@Asynchronous` to make sure they are executed on a thread pool. As mentioned in the <> section, we discourage using these methods, because the only way to obtain the future value is blocking. + +=== Inspecting Exception Cause Chains + +The `@CircuitBreaker`, `@Fallback` and `@Retry` annotations can be used to specify that certain exceptions should be treated as failures and others as successes. +This is limited to inspecting the actual exception that was thrown. +However, in many usecases, exceptions are wrapped and the exception the user wants to decide on is only present in the cause chain. + +In the non-compatible mode, if the actual thrown exception isn't known failure or known success, {smallrye-fault-tolerance} inspects the cause chain. +To be specific, in case a `@Fallback` method throws an exception, the decision process is: + +1. if the exception is assignable to one of the `skipOn` exceptions, fallback is skipped and the exception is rethrown; +2. otherwise, if the exception is assignable to one of the `applyOn` exceptions, fallback is applied; +3. otherwise, if the cause chain of the exception contains an exception assignable to one of the `skipOn` exceptions, fallback is skipped and the exception is rethrown; +4. otherwise, if the cause chain of the exception contains an exception assignable to one of the `applyOn` exceptions, fallback is applied; +5. otherwise, the exception is rethrown. + +For example, say we have this method: + +[source, java] +---- +@Fallback(fallbackMethod = "fallback", + skipOn = ExpectedOutcomeException.class, + applyOn = IOException.class) +public Result doSomething() { + ... +} + +public Result fallback() { + ... +} +---- + +If `doSomething` throws an `ExpectedOutcomeException`, fallback is skipped and the exception is thrown. +If `doSomething` throws an `IOException`, fallback is applied. +If `doSomething` throws a `WrapperException` whose cause is `ExpectedOutcomeException`, fallback is skipped and the exception is thrown. +If `doSomething` throws a `WrapperException` whose cause is `IOException`, fallback is applied. + +Comparing with the `@Fallback` specification, {smallrye-fault-tolerance} inserts 2 more steps into the decision process that inspect the cause chain. +Note that these steps are executed if and only if the thrown exception matches neither `skipOn` nor `applyOn`. +If the thrown exception matches either of them, the cause chain is not inspected at all. + +Similar behavior applies to `@CircuitBreaker` and `@Retry`. +All 3 annotations follow the same principle: exceptions considered success have priority over those considered failure. + +|=== +| Fault Tolerance annotation | Exception is first tested against | and then against + +| `@Fallback` +| `skipOn` +| `applyOn` + +| `@CircuitBreaker` +| `skipOn` +| `failOn` + +| `@Retry` +| `abortOn` +| `retryOn` +|=== diff --git a/implementation/core/src/main/java/io/smallrye/faulttolerance/core/circuit/breaker/CircuitBreaker.java b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/circuit/breaker/CircuitBreaker.java index 6dec9adb..f96120fc 100644 --- a/implementation/core/src/main/java/io/smallrye/faulttolerance/core/circuit/breaker/CircuitBreaker.java +++ b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/circuit/breaker/CircuitBreaker.java @@ -13,7 +13,7 @@ import io.smallrye.faulttolerance.core.InvocationContext; import io.smallrye.faulttolerance.core.stopwatch.RunningStopwatch; import io.smallrye.faulttolerance.core.stopwatch.Stopwatch; -import io.smallrye.faulttolerance.core.util.SetOfThrowables; +import io.smallrye.faulttolerance.core.util.ExceptionDecision; public class CircuitBreaker implements FaultToleranceStrategy { public static final int STATE_CLOSED = 0; @@ -23,8 +23,7 @@ public class CircuitBreaker implements FaultToleranceStrategy { final FaultToleranceStrategy delegate; final String description; - final SetOfThrowables failOn; - final SetOfThrowables skipOn; + private final ExceptionDecision exceptionDecision; final long delayInMillis; final int rollingWindowSize; final int failureThreshold; @@ -34,13 +33,12 @@ public class CircuitBreaker implements FaultToleranceStrategy { final AtomicReference state; @SuppressWarnings("UnnecessaryThis") - public CircuitBreaker(FaultToleranceStrategy delegate, String description, SetOfThrowables failOn, - SetOfThrowables skipOn, long delayInMillis, int requestVolumeThreshold, double failureRatio, int successThreshold, + public CircuitBreaker(FaultToleranceStrategy delegate, String description, ExceptionDecision exceptionDecision, + long delayInMillis, int requestVolumeThreshold, double failureRatio, int successThreshold, Stopwatch stopwatch) { this.delegate = checkNotNull(delegate, "Circuit breaker delegate must be set"); this.description = checkNotNull(description, "Circuit breaker description must be set"); - this.failOn = checkNotNull(failOn, "Set of fail-on throwables must be set"); - this.skipOn = checkNotNull(skipOn, "Set of skip-on throwables must be set"); + this.exceptionDecision = checkNotNull(exceptionDecision, "Exception decision must be set"); this.delayInMillis = check(delayInMillis, delayInMillis >= 0, "Circuit breaker delay must be >= 0"); this.successThreshold = check(successThreshold, successThreshold > 0, "Circuit breaker success threshold must be > 0"); this.stopwatch = checkNotNull(stopwatch, "Stopwatch must be set"); @@ -81,7 +79,7 @@ private V doApply(InvocationContext ctx) throws Exception { } boolean isConsideredSuccess(Throwable e) { - return skipOn.includes(e.getClass()) || !failOn.includes(e.getClass()); + return exceptionDecision.isConsideredExpected(e); } private V inClosed(InvocationContext ctx, State state) throws Exception { diff --git a/implementation/core/src/main/java/io/smallrye/faulttolerance/core/circuit/breaker/CompletionStageCircuitBreaker.java b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/circuit/breaker/CompletionStageCircuitBreaker.java index ce1a71e4..ddff0cc6 100644 --- a/implementation/core/src/main/java/io/smallrye/faulttolerance/core/circuit/breaker/CompletionStageCircuitBreaker.java +++ b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/circuit/breaker/CompletionStageCircuitBreaker.java @@ -11,14 +11,14 @@ import io.smallrye.faulttolerance.core.FaultToleranceStrategy; import io.smallrye.faulttolerance.core.InvocationContext; import io.smallrye.faulttolerance.core.stopwatch.Stopwatch; -import io.smallrye.faulttolerance.core.util.SetOfThrowables; +import io.smallrye.faulttolerance.core.util.ExceptionDecision; public class CompletionStageCircuitBreaker extends CircuitBreaker> { public CompletionStageCircuitBreaker(FaultToleranceStrategy> delegate, String description, - SetOfThrowables failOn, SetOfThrowables skipOn, long delayInMillis, int requestVolumeThreshold, double failureRatio, + ExceptionDecision exceptionDecision, long delayInMillis, int requestVolumeThreshold, double failureRatio, int successThreshold, Stopwatch stopwatch) { - super(delegate, description, failOn, skipOn, delayInMillis, requestVolumeThreshold, failureRatio, successThreshold, + super(delegate, description, exceptionDecision, delayInMillis, requestVolumeThreshold, failureRatio, successThreshold, stopwatch); } diff --git a/implementation/core/src/main/java/io/smallrye/faulttolerance/core/fallback/CompletionStageFallback.java b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/fallback/CompletionStageFallback.java index 6086b155..aae9ee3e 100644 --- a/implementation/core/src/main/java/io/smallrye/faulttolerance/core/fallback/CompletionStageFallback.java +++ b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/fallback/CompletionStageFallback.java @@ -9,12 +9,12 @@ import io.smallrye.faulttolerance.core.FaultToleranceStrategy; import io.smallrye.faulttolerance.core.InvocationContext; -import io.smallrye.faulttolerance.core.util.SetOfThrowables; +import io.smallrye.faulttolerance.core.util.ExceptionDecision; public class CompletionStageFallback extends Fallback> { public CompletionStageFallback(FaultToleranceStrategy> delegate, String description, - FallbackFunction> fallback, SetOfThrowables applyOn, SetOfThrowables skipOn) { - super(delegate, description, fallback, applyOn, skipOn); + FallbackFunction> fallback, ExceptionDecision exceptionDecision) { + super(delegate, description, fallback, exceptionDecision); } @Override diff --git a/implementation/core/src/main/java/io/smallrye/faulttolerance/core/fallback/Fallback.java b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/fallback/Fallback.java index 57a32829..980f9590 100644 --- a/implementation/core/src/main/java/io/smallrye/faulttolerance/core/fallback/Fallback.java +++ b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/fallback/Fallback.java @@ -6,23 +6,21 @@ import io.smallrye.faulttolerance.core.FaultToleranceStrategy; import io.smallrye.faulttolerance.core.InvocationContext; -import io.smallrye.faulttolerance.core.util.SetOfThrowables; +import io.smallrye.faulttolerance.core.util.ExceptionDecision; public class Fallback implements FaultToleranceStrategy { final FaultToleranceStrategy delegate; final String description; final FallbackFunction fallback; - final SetOfThrowables applyOn; - final SetOfThrowables skipOn; + private final ExceptionDecision exceptionDecision; public Fallback(FaultToleranceStrategy delegate, String description, FallbackFunction fallback, - SetOfThrowables applyOn, SetOfThrowables skipOn) { + ExceptionDecision exceptionDecision) { this.delegate = checkNotNull(delegate, "Fallback delegate must be set"); this.description = checkNotNull(description, "Fallback description must be set"); this.fallback = checkNotNull(fallback, "Fallback function must be set"); - this.applyOn = checkNotNull(applyOn, "Set of apply-on throwables must be set"); - this.skipOn = checkNotNull(skipOn, "Set of skip-on throwables must be set"); + this.exceptionDecision = checkNotNull(exceptionDecision, "Exception decision must be set"); } @Override @@ -60,6 +58,6 @@ private V doApply(InvocationContext ctx) throws Exception { } boolean shouldSkipFallback(Throwable e) { - return skipOn.includes(e.getClass()) || !applyOn.includes(e.getClass()); + return exceptionDecision.isConsideredExpected(e); } } diff --git a/implementation/core/src/main/java/io/smallrye/faulttolerance/core/retry/CompletionStageRetry.java b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/retry/CompletionStageRetry.java index 23740fe0..f77a0881 100644 --- a/implementation/core/src/main/java/io/smallrye/faulttolerance/core/retry/CompletionStageRetry.java +++ b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/retry/CompletionStageRetry.java @@ -16,16 +16,16 @@ import io.smallrye.faulttolerance.core.InvocationContext; import io.smallrye.faulttolerance.core.stopwatch.RunningStopwatch; import io.smallrye.faulttolerance.core.stopwatch.Stopwatch; -import io.smallrye.faulttolerance.core.util.SetOfThrowables; +import io.smallrye.faulttolerance.core.util.ExceptionDecision; public class CompletionStageRetry extends Retry> { private final Supplier delayBetweenRetries; public CompletionStageRetry(FaultToleranceStrategy> delegate, String description, - SetOfThrowables retryOn, SetOfThrowables abortOn, long maxRetries, long maxTotalDurationInMillis, + ExceptionDecision exceptionDecision, long maxRetries, long maxTotalDurationInMillis, Supplier delayBetweenRetries, Stopwatch stopwatch) { // the SyncDelay.NONE is ignored here, we have our own AsyncDelay - super(delegate, description, retryOn, abortOn, maxRetries, maxTotalDurationInMillis, SyncDelay.NONE, stopwatch); + super(delegate, description, exceptionDecision, maxRetries, maxTotalDurationInMillis, SyncDelay.NONE, stopwatch); this.delayBetweenRetries = checkNotNull(delayBetweenRetries, "Delay must be set"); } diff --git a/implementation/core/src/main/java/io/smallrye/faulttolerance/core/retry/Retry.java b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/retry/Retry.java index 81609daf..7119e9d0 100644 --- a/implementation/core/src/main/java/io/smallrye/faulttolerance/core/retry/Retry.java +++ b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/retry/Retry.java @@ -12,25 +12,23 @@ import io.smallrye.faulttolerance.core.InvocationContext; import io.smallrye.faulttolerance.core.stopwatch.RunningStopwatch; import io.smallrye.faulttolerance.core.stopwatch.Stopwatch; -import io.smallrye.faulttolerance.core.util.SetOfThrowables; +import io.smallrye.faulttolerance.core.util.ExceptionDecision; public class Retry implements FaultToleranceStrategy { final FaultToleranceStrategy delegate; final String description; - final SetOfThrowables retryOn; - final SetOfThrowables abortOn; + private final ExceptionDecision exceptionDecision; final long maxRetries; // this is an `int` in MP FT, but `long` allows easier handling of "infinity" final long maxTotalDurationInMillis; private final Supplier delayBetweenRetries; final Stopwatch stopwatch; - public Retry(FaultToleranceStrategy delegate, String description, SetOfThrowables retryOn, SetOfThrowables abortOn, + public Retry(FaultToleranceStrategy delegate, String description, ExceptionDecision exceptionDecision, long maxRetries, long maxTotalDurationInMillis, Supplier delayBetweenRetries, Stopwatch stopwatch) { this.delegate = checkNotNull(delegate, "Retry delegate must be set"); this.description = checkNotNull(description, "Retry description must be set"); - this.retryOn = checkNotNull(retryOn, "Set of retry-on throwables must be set"); - this.abortOn = checkNotNull(abortOn, "Set of abort-on throwables must be set"); + this.exceptionDecision = checkNotNull(exceptionDecision, "Exception decision must be set"); this.maxRetries = maxRetries < 0 ? Long.MAX_VALUE : maxRetries; this.maxTotalDurationInMillis = maxTotalDurationInMillis <= 0 ? Long.MAX_VALUE : maxTotalDurationInMillis; this.delayBetweenRetries = checkNotNull(delayBetweenRetries, "Delay must be set"); @@ -124,8 +122,6 @@ private V doApply(InvocationContext ctx) throws Exception { } boolean shouldAbortRetrying(Throwable e) { - // specifying `abortOn` is only useful when it's more specific than `retryOn`; - // otherwise, if the exception isn't present in `retryOn`, it's always an abort - return abortOn.includes(e.getClass()) || !retryOn.includes(e.getClass()); + return exceptionDecision.isConsideredExpected(e); } } diff --git a/implementation/core/src/main/java/io/smallrye/faulttolerance/core/util/ExceptionDecision.java b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/util/ExceptionDecision.java new file mode 100644 index 00000000..ccf4b4a4 --- /dev/null +++ b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/util/ExceptionDecision.java @@ -0,0 +1,72 @@ +package io.smallrye.faulttolerance.core.util; + +import static io.smallrye.faulttolerance.core.util.Preconditions.checkNotNull; + +import java.util.Collections; +import java.util.IdentityHashMap; +import java.util.Set; + +public class ExceptionDecision { + public static final ExceptionDecision ALWAYS_FAILURE = new ExceptionDecision(SetOfThrowables.ALL, + SetOfThrowables.EMPTY, false); + public static final ExceptionDecision ALWAYS_EXPECTED = new ExceptionDecision(SetOfThrowables.EMPTY, + SetOfThrowables.ALL, false); + + public static final ExceptionDecision EMPTY = new ExceptionDecision(SetOfThrowables.EMPTY, + SetOfThrowables.EMPTY, false); + + // @CircuitBreaker.failOn, @Fallback.applyOn, @Retry.retryOn + private final SetOfThrowables consideredFailure; + // @CircuitBreaker.skipOn, @Fallback.skipOn, @Retry.abortOn + private final SetOfThrowables consideredExpected; + + private final boolean inspectCauseChain; + + public ExceptionDecision(SetOfThrowables consideredFailure, SetOfThrowables consideredExpected, boolean inspectCauseChain) { + this.consideredFailure = checkNotNull(consideredFailure, "Set of considered-failure throwables must be set"); + this.consideredExpected = checkNotNull(consideredExpected, "Set of considered-expected throwables must be set"); + this.inspectCauseChain = inspectCauseChain; + } + + public boolean isConsideredExpected(Throwable e) { + // per `@CircuitBreaker` javadoc, `skipOn` takes priority over `failOn` + // per `@Fallback` javadoc, `skipOn` takes priority over `applyOn` + // per `@Retry` javadoc, `abortOn` takes priority over `retryOn` + // to sum up, the exceptions considered expected win over those considered failure + + if (consideredExpected.includes(e.getClass())) { + return true; + } + if (consideredFailure.includes(e.getClass())) { + return false; + } + if (!inspectCauseChain) { + return true; + } + + if (includes(consideredExpected, e)) { + return true; + } + if (includes(consideredFailure, e)) { + return false; + } + return true; + } + + private boolean includes(SetOfThrowables set, Throwable e) { + Set alreadySeen = Collections.newSetFromMap(new IdentityHashMap<>()); + + // guard against hypothetical cycle in the cause chain + while (e != null && !alreadySeen.contains(e)) { + alreadySeen.add(e); + + if (set.includes(e.getClass())) { + return true; + } + + e = e.getCause(); + } + + return false; + } +} diff --git a/implementation/core/src/main/java/io/smallrye/faulttolerance/core/util/SetOfThrowables.java b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/util/SetOfThrowables.java index b7025ed1..4819aef1 100644 --- a/implementation/core/src/main/java/io/smallrye/faulttolerance/core/util/SetOfThrowables.java +++ b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/util/SetOfThrowables.java @@ -1,21 +1,32 @@ package io.smallrye.faulttolerance.core.util; import java.util.Collections; +import java.util.HashSet; import java.util.List; import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; public class SetOfThrowables { public static final SetOfThrowables EMPTY = new SetOfThrowables(Collections.emptySet()); public static final SetOfThrowables ALL = new SetOfThrowables(Collections.singleton(Throwable.class)); /** - * @param classes classes to include - * @return a set of throwables without any additional constraints. + * Creates a set consisting of a single throwable class. The set can later be inspected using {@link #includes(Class)}. + * + * @param clazz a single throwable class to include in the set + * @return a singleton set of throwable classes + */ + public static SetOfThrowables create(Class clazz) { + return create(Collections.singletonList(clazz)); + } + + /** + * Creates a set of throwable classes that can later be inspected using {@link #includes(Class)}. + * + * @param classes throwable classes to include in the set + * @return a set of throwable classes */ public static SetOfThrowables create(List> classes) { - Set> set = Collections.newSetFromMap(new ConcurrentHashMap<>()); - set.addAll(classes); + Set> set = new HashSet<>(classes); return new SetOfThrowables(set); } diff --git a/implementation/core/src/test/java/io/smallrye/faulttolerance/core/circuit/breaker/CircuitBreakerTest.java b/implementation/core/src/test/java/io/smallrye/faulttolerance/core/circuit/breaker/CircuitBreakerTest.java index 825a74b6..e33c9fed 100644 --- a/implementation/core/src/test/java/io/smallrye/faulttolerance/core/circuit/breaker/CircuitBreakerTest.java +++ b/implementation/core/src/test/java/io/smallrye/faulttolerance/core/circuit/breaker/CircuitBreakerTest.java @@ -4,19 +4,18 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import java.util.Collections; - import org.eclipse.microprofile.faulttolerance.exceptions.CircuitBreakerOpenException; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import io.smallrye.faulttolerance.core.InvocationContext; import io.smallrye.faulttolerance.core.stopwatch.TestStopwatch; +import io.smallrye.faulttolerance.core.util.ExceptionDecision; import io.smallrye.faulttolerance.core.util.SetOfThrowables; import io.smallrye.faulttolerance.core.util.TestException; public class CircuitBreakerTest { - private static final SetOfThrowables testException = SetOfThrowables.create(Collections.singletonList(TestException.class)); + private static final SetOfThrowables testException = SetOfThrowables.create(TestException.class); private TestStopwatch stopwatch; @@ -27,8 +26,9 @@ public void setUp() { @Test public void test1() throws Exception { - CircuitBreaker cb = new CircuitBreaker<>(invocation(), "test invocation", testException, - SetOfThrowables.EMPTY, 1000, 4, 0.5, 2, stopwatch); + CircuitBreaker cb = new CircuitBreaker<>(invocation(), "test invocation", + new ExceptionDecision(testException, SetOfThrowables.EMPTY, false), + 1000, 4, 0.5, 2, stopwatch); // circuit breaker is closed assertThat(cb.apply(new InvocationContext<>(() -> "foobar1"))).isEqualTo("foobar1"); @@ -78,8 +78,9 @@ public void test1() throws Exception { @Test public void test2() throws Exception { - CircuitBreaker cb = new CircuitBreaker<>(invocation(), "test invocation", testException, - SetOfThrowables.EMPTY, 1000, 4, 0.5, 2, stopwatch); + CircuitBreaker cb = new CircuitBreaker<>(invocation(), "test invocation", + new ExceptionDecision(testException, SetOfThrowables.EMPTY, false), + 1000, 4, 0.5, 2, stopwatch); // circuit breaker is closed assertThat(cb.apply(new InvocationContext<>(() -> "foobar1"))).isEqualTo("foobar1"); diff --git a/implementation/core/src/test/java/io/smallrye/faulttolerance/core/circuit/breaker/CompletionStageCircuitBreakerTest.java b/implementation/core/src/test/java/io/smallrye/faulttolerance/core/circuit/breaker/CompletionStageCircuitBreakerTest.java index fdf5cf63..e555b5ff 100644 --- a/implementation/core/src/test/java/io/smallrye/faulttolerance/core/circuit/breaker/CompletionStageCircuitBreakerTest.java +++ b/implementation/core/src/test/java/io/smallrye/faulttolerance/core/circuit/breaker/CompletionStageCircuitBreakerTest.java @@ -6,7 +6,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import java.util.Collections; import java.util.concurrent.CompletionStage; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; @@ -21,11 +20,12 @@ import io.smallrye.faulttolerance.core.InvocationContext; import io.smallrye.faulttolerance.core.async.CompletionStageExecution; import io.smallrye.faulttolerance.core.stopwatch.TestStopwatch; +import io.smallrye.faulttolerance.core.util.ExceptionDecision; import io.smallrye.faulttolerance.core.util.SetOfThrowables; import io.smallrye.faulttolerance.core.util.TestException; public class CompletionStageCircuitBreakerTest { - private static final SetOfThrowables testException = SetOfThrowables.create(Collections.singletonList(TestException.class)); + private static final SetOfThrowables testException = SetOfThrowables.create(TestException.class); private TestStopwatch stopwatch; @@ -48,7 +48,7 @@ public void tearDown() throws InterruptedException { public void test1() throws Exception { CompletionStageExecution execution = new CompletionStageExecution<>(invocation(), executor); CompletionStageCircuitBreaker cb = new CompletionStageCircuitBreaker<>(execution, "test invocation", - testException, SetOfThrowables.EMPTY, + new ExceptionDecision(testException, SetOfThrowables.EMPTY, false), 1000, 4, 0.5, 2, stopwatch); // circuit breaker is closed @@ -101,7 +101,7 @@ public void test1() throws Exception { public void test2() throws Exception { CompletionStageExecution execution = new CompletionStageExecution<>(invocation(), executor); CompletionStageCircuitBreaker cb = new CompletionStageCircuitBreaker<>(execution, "test invocation", - testException, SetOfThrowables.EMPTY, + new ExceptionDecision(testException, SetOfThrowables.EMPTY, false), 1000, 4, 0.5, 2, stopwatch); // circuit breaker is closed @@ -136,7 +136,7 @@ public void shouldTreatCompletionStageFailureAsCBFailure() throws Exception { CompletionStageExecution execution = new CompletionStageExecution<>(invocation(), executor); CompletionStageCircuitBreaker cb = new CompletionStageCircuitBreaker<>(execution, "test invocation", - testException, SetOfThrowables.EMPTY, + new ExceptionDecision(testException, SetOfThrowables.EMPTY, false), 1000, 4, 0.5, 2, stopwatch); assertThatThrownBy(cb.apply(eventuallyFailingWith(exception)).toCompletableFuture()::get) diff --git a/implementation/core/src/test/java/io/smallrye/faulttolerance/core/circuit/breaker/FutureCircuitBreakerTest.java b/implementation/core/src/test/java/io/smallrye/faulttolerance/core/circuit/breaker/FutureCircuitBreakerTest.java index 2d645d46..23b80658 100644 --- a/implementation/core/src/test/java/io/smallrye/faulttolerance/core/circuit/breaker/FutureCircuitBreakerTest.java +++ b/implementation/core/src/test/java/io/smallrye/faulttolerance/core/circuit/breaker/FutureCircuitBreakerTest.java @@ -5,7 +5,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import java.util.Collections; import java.util.concurrent.Future; import org.eclipse.microprofile.faulttolerance.exceptions.CircuitBreakerOpenException; @@ -14,11 +13,12 @@ import io.smallrye.faulttolerance.core.InvocationContext; import io.smallrye.faulttolerance.core.stopwatch.TestStopwatch; +import io.smallrye.faulttolerance.core.util.ExceptionDecision; import io.smallrye.faulttolerance.core.util.SetOfThrowables; import io.smallrye.faulttolerance.core.util.TestException; public class FutureCircuitBreakerTest { - private static final SetOfThrowables testException = SetOfThrowables.create(Collections.singletonList(TestException.class)); + private static final SetOfThrowables testException = SetOfThrowables.create(TestException.class); private TestStopwatch stopwatch; @@ -29,8 +29,8 @@ public void setUp() { @Test public void test1() throws Exception { - CircuitBreaker> cb = new CircuitBreaker<>(invocation(), - "test invocation", testException, SetOfThrowables.EMPTY, + CircuitBreaker> cb = new CircuitBreaker<>(invocation(), "test invocation", + new ExceptionDecision(testException, SetOfThrowables.EMPTY, false), 1000, 4, 0.5, 2, stopwatch); // circuit breaker is closed @@ -86,8 +86,8 @@ public void test1() throws Exception { @Test public void test2() throws Exception { - CircuitBreaker> cb = new CircuitBreaker<>(invocation(), - "test invocation", testException, SetOfThrowables.EMPTY, + CircuitBreaker> cb = new CircuitBreaker<>(invocation(), "test invocation", + new ExceptionDecision(testException, SetOfThrowables.EMPTY, false), 1000, 4, 0.5, 2, stopwatch); // circuit breaker is closed diff --git a/implementation/core/src/test/java/io/smallrye/faulttolerance/core/composition/Strategies.java b/implementation/core/src/test/java/io/smallrye/faulttolerance/core/composition/Strategies.java index 6e2b9691..fa304a56 100644 --- a/implementation/core/src/test/java/io/smallrye/faulttolerance/core/composition/Strategies.java +++ b/implementation/core/src/test/java/io/smallrye/faulttolerance/core/composition/Strategies.java @@ -1,14 +1,12 @@ package io.smallrye.faulttolerance.core.composition; -import java.util.Collections; - import io.smallrye.faulttolerance.core.FaultToleranceStrategy; import io.smallrye.faulttolerance.core.circuit.breaker.CircuitBreaker; import io.smallrye.faulttolerance.core.fallback.Fallback; import io.smallrye.faulttolerance.core.retry.Retry; import io.smallrye.faulttolerance.core.retry.SyncDelay; import io.smallrye.faulttolerance.core.stopwatch.TestStopwatch; -import io.smallrye.faulttolerance.core.util.SetOfThrowables; +import io.smallrye.faulttolerance.core.util.ExceptionDecision; /** * Factory methods for fault tolerance strategies that are easier to use than their constructors. @@ -18,13 +16,12 @@ final class Strategies { static Fallback fallback(FaultToleranceStrategy delegate) { return new Fallback<>(delegate, "fallback", ctx -> "fallback after " + ctx.failure.getClass().getSimpleName(), - SetOfThrowables.ALL, SetOfThrowables.EMPTY); + ExceptionDecision.ALWAYS_FAILURE); } static Retry retry(FaultToleranceStrategy delegate) { - return new Retry<>(delegate, "retry", - SetOfThrowables.create(Collections.singletonList(Exception.class)), - SetOfThrowables.EMPTY, 10, 0, SyncDelay.NONE, new TestStopwatch()); + return new Retry<>(delegate, "retry", ExceptionDecision.ALWAYS_FAILURE, + 10, 0, SyncDelay.NONE, new TestStopwatch()); } static CircuitBreaker circuitBreaker(FaultToleranceStrategy delegate) { @@ -32,8 +29,7 @@ static CircuitBreaker circuitBreaker(FaultToleranceStrategy delegate) } static CircuitBreaker circuitBreaker(FaultToleranceStrategy delegate, int delayInMillis) { - return new CircuitBreaker<>(delegate, "circuit breaker", - SetOfThrowables.ALL, SetOfThrowables.EMPTY, delayInMillis, 5, 0.2, 3, - new TestStopwatch()); + return new CircuitBreaker<>(delegate, "circuit breaker", ExceptionDecision.ALWAYS_FAILURE, + delayInMillis, 5, 0.2, 3, new TestStopwatch()); } } diff --git a/implementation/core/src/test/java/io/smallrye/faulttolerance/core/fallback/CompletionStageFallbackTest.java b/implementation/core/src/test/java/io/smallrye/faulttolerance/core/fallback/CompletionStageFallbackTest.java index 9bbfa4da..ad99ea8b 100644 --- a/implementation/core/src/test/java/io/smallrye/faulttolerance/core/fallback/CompletionStageFallbackTest.java +++ b/implementation/core/src/test/java/io/smallrye/faulttolerance/core/fallback/CompletionStageFallbackTest.java @@ -13,7 +13,7 @@ import io.smallrye.faulttolerance.core.InvocationContext; import io.smallrye.faulttolerance.core.async.CompletionStageExecution; -import io.smallrye.faulttolerance.core.util.SetOfThrowables; +import io.smallrye.faulttolerance.core.util.ExceptionDecision; import io.smallrye.faulttolerance.core.util.TestException; import io.smallrye.faulttolerance.core.util.TestExecutor; import io.smallrye.faulttolerance.core.util.TestInvocation; @@ -32,7 +32,7 @@ public void allExceptionsSupported_valueThenValue() throws Exception { CompletionStageExecution execution = new CompletionStageExecution<>(invocation, executor); CompletionStageFallback fallback = new CompletionStageFallback<>(execution, "test invocation", ctx -> completedStage("fallback"), - SetOfThrowables.ALL, SetOfThrowables.EMPTY); + ExceptionDecision.ALWAYS_FAILURE); CompletionStage result = fallback.apply(new InvocationContext<>(null)); assertThat(result.toCompletableFuture().get()).isEqualTo("foobar"); } @@ -43,7 +43,7 @@ public void allExceptionsSupported_valueThenDirectException() throws Exception { CompletionStageExecution execution = new CompletionStageExecution<>(invocation, executor); CompletionStageFallback fallback = new CompletionStageFallback<>(execution, "test invocation", ctx -> TestException.doThrow(), - SetOfThrowables.ALL, SetOfThrowables.EMPTY); + ExceptionDecision.ALWAYS_FAILURE); CompletionStage result = fallback.apply(new InvocationContext<>(null)); assertThat(result.toCompletableFuture().get()).isEqualTo("foobar"); } @@ -54,7 +54,7 @@ public void allExceptionsSupported_valueThenCompletionStageException() throws Ex CompletionStageExecution execution = new CompletionStageExecution<>(invocation, executor); CompletionStageFallback fallback = new CompletionStageFallback<>(execution, "test invocation", ctx -> failedStage(new TestException()), - SetOfThrowables.ALL, SetOfThrowables.EMPTY); + ExceptionDecision.ALWAYS_FAILURE); CompletionStage result = fallback.apply(new InvocationContext<>(null)); assertThat(result.toCompletableFuture().get()).isEqualTo("foobar"); } @@ -65,7 +65,7 @@ public void allExceptionsSupported_directExceptionThenValue() throws Exception { CompletionStageExecution execution = new CompletionStageExecution<>(invocation, executor); CompletionStageFallback fallback = new CompletionStageFallback<>(execution, "test invocation", ctx -> completedStage("fallback"), - SetOfThrowables.ALL, SetOfThrowables.EMPTY); + ExceptionDecision.ALWAYS_FAILURE); CompletionStage result = fallback.apply(new InvocationContext<>(null)); assertThat(result.toCompletableFuture().get()).isEqualTo("fallback"); } @@ -76,7 +76,7 @@ public void allExceptionsSupported_completionStageExceptionThenValue() throws Ex CompletionStageExecution execution = new CompletionStageExecution<>(invocation, executor); CompletionStageFallback fallback = new CompletionStageFallback<>(execution, "test invocation", ctx -> completedStage("fallback"), - SetOfThrowables.ALL, SetOfThrowables.EMPTY); + ExceptionDecision.ALWAYS_FAILURE); CompletionStage result = fallback.apply(new InvocationContext<>(null)); assertThat(result.toCompletableFuture().get()).isEqualTo("fallback"); } @@ -88,7 +88,7 @@ public void allExceptionsSupported_directExceptionThenDirectException() { CompletionStageFallback fallback = new CompletionStageFallback<>(execution, "test invocation", ctx -> { throw new RuntimeException(); - }, SetOfThrowables.ALL, SetOfThrowables.EMPTY); + }, ExceptionDecision.ALWAYS_FAILURE); CompletionStage result = fallback.apply(new InvocationContext<>(null)); assertThatThrownBy(result.toCompletableFuture()::get) .isExactlyInstanceOf(ExecutionException.class) @@ -101,7 +101,7 @@ public void allExceptionsSupported_directExceptionThenCompletionStageException() CompletionStageExecution execution = new CompletionStageExecution<>(invocation, executor); CompletionStageFallback fallback = new CompletionStageFallback<>(execution, "test invocation", ctx -> failedStage(new RuntimeException()), - SetOfThrowables.ALL, SetOfThrowables.EMPTY); + ExceptionDecision.ALWAYS_FAILURE); CompletionStage result = fallback.apply(new InvocationContext<>(null)); assertThatThrownBy(result.toCompletableFuture()::get) .isExactlyInstanceOf(ExecutionException.class) @@ -115,7 +115,7 @@ public void allExceptionsSupported_completionStageExceptionThenDirectException() CompletionStageFallback fallback = new CompletionStageFallback<>(execution, "test invocation", ctx -> { throw new RuntimeException(); - }, SetOfThrowables.ALL, SetOfThrowables.EMPTY); + }, ExceptionDecision.ALWAYS_FAILURE); CompletionStage result = fallback.apply(new InvocationContext<>(null)); assertThatThrownBy(result.toCompletableFuture()::get) .isExactlyInstanceOf(ExecutionException.class) @@ -128,7 +128,7 @@ public void allExceptionsSupported_completionStageExceptionThenCompletionStageEx CompletionStageExecution execution = new CompletionStageExecution<>(invocation, executor); CompletionStageFallback fallback = new CompletionStageFallback<>(execution, "test invocation", ctx -> failedStage(new RuntimeException()), - SetOfThrowables.ALL, SetOfThrowables.EMPTY); + ExceptionDecision.ALWAYS_FAILURE); CompletionStage result = fallback.apply(new InvocationContext<>(null)); assertThatThrownBy(result.toCompletableFuture()::get) .isExactlyInstanceOf(ExecutionException.class) @@ -141,7 +141,7 @@ public void noExceptionSupported_valueThenValue() throws Exception { CompletionStageExecution execution = new CompletionStageExecution<>(invocation, executor); CompletionStageFallback fallback = new CompletionStageFallback<>(execution, "test invocation", ctx -> completedStage("fallback"), - SetOfThrowables.EMPTY, SetOfThrowables.ALL); + ExceptionDecision.ALWAYS_EXPECTED); CompletionStage result = fallback.apply(new InvocationContext<>(null)); assertThat(result.toCompletableFuture().get()).isEqualTo("foobar"); } @@ -152,7 +152,7 @@ public void noExceptionSupported_valueThenDirectException() throws Exception { CompletionStageExecution execution = new CompletionStageExecution<>(invocation, executor); CompletionStageFallback fallback = new CompletionStageFallback<>(execution, "test invocation", ctx -> TestException.doThrow(), - SetOfThrowables.EMPTY, SetOfThrowables.ALL); + ExceptionDecision.ALWAYS_EXPECTED); CompletionStage result = fallback.apply(new InvocationContext<>(null)); assertThat(result.toCompletableFuture().get()).isEqualTo("foobar"); } @@ -163,7 +163,7 @@ public void noExceptionSupported_valueThenCompletionStageException() throws Exce CompletionStageExecution execution = new CompletionStageExecution<>(invocation, executor); CompletionStageFallback fallback = new CompletionStageFallback<>(execution, "test invocation", ctx -> failedStage(new TestException()), - SetOfThrowables.EMPTY, SetOfThrowables.ALL); + ExceptionDecision.ALWAYS_EXPECTED); CompletionStage result = fallback.apply(new InvocationContext<>(null)); assertThat(result.toCompletableFuture().get()).isEqualTo("foobar"); } @@ -174,7 +174,7 @@ public void noExceptionSupported_directExceptionThenValue() throws Exception { CompletionStageExecution execution = new CompletionStageExecution<>(invocation, executor); CompletionStageFallback fallback = new CompletionStageFallback<>(execution, "test invocation", ctx -> completedStage("fallback"), - SetOfThrowables.EMPTY, SetOfThrowables.ALL); + ExceptionDecision.ALWAYS_EXPECTED); CompletionStage result = fallback.apply(new InvocationContext<>(null)); assertThatThrownBy(result.toCompletableFuture()::get) .isExactlyInstanceOf(ExecutionException.class) @@ -187,7 +187,7 @@ public void noExceptionSupported_completionStageExceptionThenValue() throws Exce CompletionStageExecution execution = new CompletionStageExecution<>(invocation, executor); CompletionStageFallback fallback = new CompletionStageFallback<>(execution, "test invocation", ctx -> completedStage("fallback"), - SetOfThrowables.EMPTY, SetOfThrowables.ALL); + ExceptionDecision.ALWAYS_EXPECTED); CompletionStage result = fallback.apply(new InvocationContext<>(null)); assertThatThrownBy(result.toCompletableFuture()::get) .isExactlyInstanceOf(ExecutionException.class) @@ -201,7 +201,7 @@ public void noExceptionSupported_directExceptionThenDirectException() { CompletionStageFallback fallback = new CompletionStageFallback<>(execution, "test invocation", ctx -> { throw new RuntimeException(); - }, SetOfThrowables.EMPTY, SetOfThrowables.ALL); + }, ExceptionDecision.ALWAYS_EXPECTED); CompletionStage result = fallback.apply(new InvocationContext<>(null)); assertThatThrownBy(result.toCompletableFuture()::get) .isExactlyInstanceOf(ExecutionException.class) @@ -214,7 +214,7 @@ public void noExceptionSupported_directExceptionThenCompletionStageException() { CompletionStageExecution execution = new CompletionStageExecution<>(invocation, executor); CompletionStageFallback fallback = new CompletionStageFallback<>(execution, "test invocation", ctx -> failedStage(new RuntimeException()), - SetOfThrowables.EMPTY, SetOfThrowables.ALL); + ExceptionDecision.ALWAYS_EXPECTED); CompletionStage result = fallback.apply(new InvocationContext<>(null)); assertThatThrownBy(result.toCompletableFuture()::get) .isExactlyInstanceOf(ExecutionException.class) @@ -228,7 +228,7 @@ public void noExceptionSupported_completionStageExceptionThenDirectException() { CompletionStageFallback fallback = new CompletionStageFallback<>(execution, "test invocation", ctx -> { throw new RuntimeException(); - }, SetOfThrowables.EMPTY, SetOfThrowables.ALL); + }, ExceptionDecision.ALWAYS_EXPECTED); CompletionStage result = fallback.apply(new InvocationContext<>(null)); assertThatThrownBy(result.toCompletableFuture()::get) .isExactlyInstanceOf(ExecutionException.class) @@ -241,7 +241,7 @@ public void noExceptionSupported_completionStageExceptionThenCompletionStageExce CompletionStageExecution execution = new CompletionStageExecution<>(invocation, executor); CompletionStageFallback fallback = new CompletionStageFallback<>(execution, "test invocation", ctx -> failedStage(new RuntimeException()), - SetOfThrowables.EMPTY, SetOfThrowables.ALL); + ExceptionDecision.ALWAYS_EXPECTED); CompletionStage result = fallback.apply(new InvocationContext<>(null)); assertThatThrownBy(result.toCompletableFuture()::get) .isExactlyInstanceOf(ExecutionException.class) diff --git a/implementation/core/src/test/java/io/smallrye/faulttolerance/core/fallback/FallbackTest.java b/implementation/core/src/test/java/io/smallrye/faulttolerance/core/fallback/FallbackTest.java index 0a82a862..a0de3974 100644 --- a/implementation/core/src/test/java/io/smallrye/faulttolerance/core/fallback/FallbackTest.java +++ b/implementation/core/src/test/java/io/smallrye/faulttolerance/core/fallback/FallbackTest.java @@ -7,7 +7,7 @@ import org.junit.jupiter.api.Test; import io.smallrye.faulttolerance.core.FaultToleranceStrategy; -import io.smallrye.faulttolerance.core.util.SetOfThrowables; +import io.smallrye.faulttolerance.core.util.ExceptionDecision; import io.smallrye.faulttolerance.core.util.TestException; import io.smallrye.faulttolerance.core.util.TestInvocation; import io.smallrye.faulttolerance.core.util.TestThread; @@ -18,7 +18,7 @@ public class FallbackTest { public void immediatelyReturning_allExceptionsSupported_valueThenValue() throws Exception { TestInvocation invocation = TestInvocation.of(() -> "foobar"); TestThread result = runOnTestThread(new Fallback<>(invocation, "test invocation", - ctx -> "fallback", SetOfThrowables.ALL, SetOfThrowables.EMPTY)); + ctx -> "fallback", ExceptionDecision.ALWAYS_FAILURE)); assertThat(result.await()).isEqualTo("foobar"); } @@ -26,7 +26,7 @@ public void immediatelyReturning_allExceptionsSupported_valueThenValue() throws public void immediatelyReturning_allExceptionsSupported_valueThenException() throws Exception { TestInvocation invocation = TestInvocation.of(() -> "foobar"); TestThread result = runOnTestThread(new Fallback<>(invocation, "test invocation", - ctx -> TestException.doThrow(), SetOfThrowables.ALL, SetOfThrowables.EMPTY)); + ctx -> TestException.doThrow(), ExceptionDecision.ALWAYS_FAILURE)); assertThat(result.await()).isEqualTo("foobar"); } @@ -34,7 +34,7 @@ public void immediatelyReturning_allExceptionsSupported_valueThenException() thr public void immediatelyReturning_allExceptionsSupported_exceptionThenValue() throws Exception { TestInvocation invocation = TestInvocation.of(TestException::doThrow); TestThread result = runOnTestThread(new Fallback<>(invocation, "test invocation", - ctx -> "fallback", SetOfThrowables.ALL, SetOfThrowables.EMPTY)); + ctx -> "fallback", ExceptionDecision.ALWAYS_FAILURE)); assertThat(result.await()).isEqualTo("fallback"); } @@ -43,7 +43,7 @@ public void immediatelyReturning_allExceptionsSupported_exceptionThenException() TestInvocation invocation = TestInvocation.of(TestException::doThrow); TestThread result = runOnTestThread(new Fallback<>(invocation, "test invocation", ctx -> { throw new RuntimeException(); - }, SetOfThrowables.ALL, SetOfThrowables.EMPTY)); + }, ExceptionDecision.ALWAYS_FAILURE)); assertThatThrownBy(result::await).isExactlyInstanceOf(RuntimeException.class); } @@ -51,7 +51,7 @@ public void immediatelyReturning_allExceptionsSupported_exceptionThenException() public void immediatelyReturning_noExceptionSupported_valueThenValue() throws Exception { TestInvocation invocation = TestInvocation.of(() -> "foobar"); TestThread result = runOnTestThread(new Fallback<>(invocation, "test invocation", - ctx -> "fallback", SetOfThrowables.EMPTY, SetOfThrowables.ALL)); + ctx -> "fallback", ExceptionDecision.ALWAYS_EXPECTED)); assertThat(result.await()).isEqualTo("foobar"); } @@ -59,7 +59,7 @@ public void immediatelyReturning_noExceptionSupported_valueThenValue() throws Ex public void immediatelyReturning_noExceptionSupported_valueThenException() throws Exception { TestInvocation invocation = TestInvocation.of(() -> "foobar"); TestThread result = runOnTestThread(new Fallback<>(invocation, "test invocation", - ctx -> TestException.doThrow(), SetOfThrowables.EMPTY, SetOfThrowables.ALL)); + ctx -> TestException.doThrow(), ExceptionDecision.ALWAYS_EXPECTED)); assertThat(result.await()).isEqualTo("foobar"); } @@ -67,7 +67,7 @@ public void immediatelyReturning_noExceptionSupported_valueThenException() throw public void immediatelyReturning_noExceptionSupported_exceptionThenValue() throws Exception { TestInvocation invocation = TestInvocation.of(TestException::doThrow); TestThread result = runOnTestThread(new Fallback<>(invocation, "test invocation", - ctx -> "fallback", SetOfThrowables.EMPTY, SetOfThrowables.ALL)); + ctx -> "fallback", ExceptionDecision.ALWAYS_EXPECTED)); assertThatThrownBy(result::await).isExactlyInstanceOf(TestException.class); } @@ -76,7 +76,7 @@ public void immediatelyReturning_noExceptionSupported_exceptionThenException() { TestInvocation invocation = TestInvocation.of(TestException::doThrow); TestThread result = runOnTestThread(new Fallback<>(invocation, "test invocation", ctx -> { throw new RuntimeException(); - }, SetOfThrowables.EMPTY, SetOfThrowables.ALL)); + }, ExceptionDecision.ALWAYS_EXPECTED)); assertThatThrownBy(result::await).isExactlyInstanceOf(TestException.class); } @@ -91,7 +91,7 @@ public void waitingOnParty_interruptedInInvocation() throws InterruptedException return "foobar"; }); TestThread executingThread = runOnTestThread(new Fallback<>(invocation, "test invocation", - ctx -> "fallback", SetOfThrowables.ALL, SetOfThrowables.EMPTY)); + ctx -> "fallback", ExceptionDecision.ALWAYS_FAILURE)); party.organizer().waitForAll(); executingThread.interrupt(); assertThatThrownBy(executingThread::await).isExactlyInstanceOf(InterruptedException.class); @@ -106,7 +106,7 @@ public void waitingOnParty_interruptedInFallback() throws InterruptedException { return "fallback"; }; TestThread executingThread = runOnTestThread(new Fallback<>(invocation, "test invocation", - fallback, SetOfThrowables.ALL, SetOfThrowables.EMPTY)); + fallback, ExceptionDecision.ALWAYS_FAILURE)); party.organizer().waitForAll(); executingThread.interrupt(); assertThatThrownBy(executingThread::await).isExactlyInstanceOf(InterruptedException.class); @@ -119,7 +119,7 @@ public void selfInterruptedInInvocation_value() throws Exception { return "foobar"; }; TestThread result = runOnTestThread(new Fallback<>(invocation, "test invocation", - ctx -> "fallback", SetOfThrowables.ALL, SetOfThrowables.EMPTY)); + ctx -> "fallback", ExceptionDecision.ALWAYS_FAILURE)); assertThat(result.await()).isEqualTo("foobar"); } @@ -130,7 +130,7 @@ public void selfInterruptedInInvocation_exception() { throw new RuntimeException(); }; TestThread executingThread = runOnTestThread(new Fallback<>(invocation, "test invocation", - ctx -> "fallback", SetOfThrowables.ALL, SetOfThrowables.EMPTY)); + ctx -> "fallback", ExceptionDecision.ALWAYS_FAILURE)); assertThatThrownBy(executingThread::await).isExactlyInstanceOf(InterruptedException.class); } @@ -142,7 +142,7 @@ public void selfInterruptedInFallback_value() throws Exception { return "fallback"; }; TestThread result = runOnTestThread(new Fallback<>(invocation, "test invocation", - fallback, SetOfThrowables.ALL, SetOfThrowables.EMPTY)); + fallback, ExceptionDecision.ALWAYS_FAILURE)); assertThat(result.await()).isEqualTo("fallback"); } @@ -154,7 +154,7 @@ public void selfInterruptedInFallback_exception() { throw new RuntimeException(); }; TestThread executingThread = runOnTestThread(new Fallback<>(invocation, "test invocation", - fallback, SetOfThrowables.ALL, SetOfThrowables.EMPTY)); + fallback, ExceptionDecision.ALWAYS_FAILURE)); assertThatThrownBy(executingThread::await).isExactlyInstanceOf(RuntimeException.class); } } diff --git a/implementation/core/src/test/java/io/smallrye/faulttolerance/core/fallback/FutureFallbackTest.java b/implementation/core/src/test/java/io/smallrye/faulttolerance/core/fallback/FutureFallbackTest.java index 1a547928..4b9d12a1 100644 --- a/implementation/core/src/test/java/io/smallrye/faulttolerance/core/fallback/FutureFallbackTest.java +++ b/implementation/core/src/test/java/io/smallrye/faulttolerance/core/fallback/FutureFallbackTest.java @@ -10,7 +10,7 @@ import org.junit.jupiter.api.Test; -import io.smallrye.faulttolerance.core.util.SetOfThrowables; +import io.smallrye.faulttolerance.core.util.ExceptionDecision; import io.smallrye.faulttolerance.core.util.TestException; import io.smallrye.faulttolerance.core.util.TestInvocation; import io.smallrye.faulttolerance.core.util.TestThread; @@ -22,7 +22,7 @@ public void shouldNotFallBackOnFailingFuture() throws Exception { TestInvocation> invocation = TestInvocation.of( () -> failedFuture(forcedException)); TestThread> result = runOnTestThread(new Fallback<>(invocation, "test invocation", - this::fallback, SetOfThrowables.ALL, SetOfThrowables.EMPTY)); + this::fallback, ExceptionDecision.ALWAYS_FAILURE)); Future future = result.await(); assertThatThrownBy(future::get).hasCause(forcedException); } @@ -34,7 +34,7 @@ public void shouldFallbackOnFailureToCreateFuture() throws Exception { throw forcedException; }); TestThread> result = runOnTestThread(new Fallback<>(invocation, "test invocation", - this::fallback, SetOfThrowables.ALL, SetOfThrowables.EMPTY)); + this::fallback, ExceptionDecision.ALWAYS_FAILURE)); Future await = result.await(); assertThat(await.get()).isEqualTo("fallback"); } @@ -43,7 +43,7 @@ public void shouldFallbackOnFailureToCreateFuture() throws Exception { public void shouldSucceed() throws Exception { TestInvocation> invocation = TestInvocation.of(() -> completedFuture("invocation")); TestThread> result = runOnTestThread(new Fallback<>(invocation, "test invocation", - this::fallback, SetOfThrowables.ALL, SetOfThrowables.EMPTY)); + this::fallback, ExceptionDecision.ALWAYS_FAILURE)); Future future = result.await(); assertThat(future.get()).isEqualTo("invocation"); } @@ -53,11 +53,11 @@ public void immediatelyReturning_exceptionThenException() { TestInvocation invocation = TestInvocation.of(TestException::doThrow); TestThread result = runOnTestThread(new Fallback<>(invocation, "test invocation", e -> { throw new RuntimeException(); - }, SetOfThrowables.ALL, SetOfThrowables.EMPTY)); + }, ExceptionDecision.ALWAYS_FAILURE)); assertThatThrownBy(result::await).isExactlyInstanceOf(RuntimeException.class); } - private Future fallback(FallbackContext ctx) { + private Future fallback(FallbackContext ctx) { return completedFuture("fallback"); } } diff --git a/implementation/core/src/test/java/io/smallrye/faulttolerance/core/retry/CompletionStageRetryTest.java b/implementation/core/src/test/java/io/smallrye/faulttolerance/core/retry/CompletionStageRetryTest.java index 0dad0fb2..127598b4 100644 --- a/implementation/core/src/test/java/io/smallrye/faulttolerance/core/retry/CompletionStageRetryTest.java +++ b/implementation/core/src/test/java/io/smallrye/faulttolerance/core/retry/CompletionStageRetryTest.java @@ -4,7 +4,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import java.util.Collections; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; import java.util.concurrent.ExecutionException; @@ -20,6 +19,7 @@ import io.smallrye.faulttolerance.core.InvocationContext; import io.smallrye.faulttolerance.core.async.CompletionStageExecution; import io.smallrye.faulttolerance.core.stopwatch.TestStopwatch; +import io.smallrye.faulttolerance.core.util.ExceptionDecision; import io.smallrye.faulttolerance.core.util.SetOfThrowables; public class CompletionStageRetryTest { @@ -46,8 +46,7 @@ public void shouldNotRetryOnSuccess() throws Exception { }); CompletionStageExecution execution = new CompletionStageExecution<>(invocation, executor); CompletionStageRetry retry = new CompletionStageRetry<>(execution, "shouldNotRetryOnSuccess", - SetOfThrowables.ALL, SetOfThrowables.EMPTY, - 3, 1000L, AsyncDelay.NONE, new TestStopwatch()); + ExceptionDecision.ALWAYS_FAILURE, 3, 1000L, AsyncDelay.NONE, new TestStopwatch()); CompletionStage result = retry.apply(new InvocationContext<>(() -> completedFuture("ignored"))); assertThat(result.toCompletableFuture().get()).isEqualTo("shouldNotRetryOnSuccess"); @@ -68,7 +67,7 @@ public void shouldPropagateAbortOnError() { }); CompletionStageExecution execution = new CompletionStageExecution<>(invocation, executor); CompletionStageRetry retry = new CompletionStageRetry<>(execution, "shouldPropagateAbortOnError", - SetOfThrowables.ALL, SetOfThrowables.create(Collections.singletonList(RuntimeException.class)), + new ExceptionDecision(SetOfThrowables.ALL, SetOfThrowables.create(RuntimeException.class), false), 3, 1000L, AsyncDelay.NONE, new TestStopwatch()); CompletionStage result = retry.apply(new InvocationContext<>(() -> completedFuture("ignored"))); @@ -89,7 +88,7 @@ public void shouldPropagateAbortOnErrorInCSCreation() { }); CompletionStageExecution execution = new CompletionStageExecution<>(invocation, executor); CompletionStageRetry retry = new CompletionStageRetry<>(execution, "shouldPropagateAbortOnErrorInCSCreation", - SetOfThrowables.ALL, SetOfThrowables.create(Collections.singletonList(RuntimeException.class)), + new ExceptionDecision(SetOfThrowables.ALL, SetOfThrowables.create(RuntimeException.class), false), 3, 1000L, AsyncDelay.NONE, new TestStopwatch()); CompletionStage result = retry.apply(new InvocationContext<>(() -> completedFuture("ignored"))); @@ -114,7 +113,7 @@ public void shouldRetryOnce() throws Exception { }); CompletionStageExecution execution = new CompletionStageExecution<>(invocation, executor); CompletionStageRetry retry = new CompletionStageRetry<>(execution, "shouldRetryOnce", - SetOfThrowables.create(Collections.singletonList(RuntimeException.class)), SetOfThrowables.EMPTY, + new ExceptionDecision(SetOfThrowables.create(RuntimeException.class), SetOfThrowables.EMPTY, false), 3, 1000L, AsyncDelay.NONE, new TestStopwatch()); CompletionStage result = retry.apply(new InvocationContext<>(() -> completedFuture("ignored"))); @@ -140,7 +139,7 @@ public void shouldRetryOnceOnCsFailure() throws Exception { }); CompletionStageExecution execution = new CompletionStageExecution<>(invocation, executor); CompletionStageRetry retry = new CompletionStageRetry<>(execution, "shouldRetryOnceOnCsFailure", - SetOfThrowables.create(Collections.singletonList(RuntimeException.class)), SetOfThrowables.EMPTY, + new ExceptionDecision(SetOfThrowables.create(RuntimeException.class), SetOfThrowables.EMPTY, false), 3, 1000L, AsyncDelay.NONE, new TestStopwatch()); CompletionStage result = retry.apply(new InvocationContext<>(() -> completedFuture("ignored"))); @@ -166,7 +165,7 @@ public void shouldRetryMaxTimesAndSucceed() throws Exception { }); CompletionStageExecution execution = new CompletionStageExecution<>(invocation, executor); CompletionStageRetry retry = new CompletionStageRetry<>(execution, "shouldRetryMaxTimesAndSucceed", - SetOfThrowables.create(Collections.singletonList(RuntimeException.class)), SetOfThrowables.EMPTY, + new ExceptionDecision(SetOfThrowables.create(RuntimeException.class), SetOfThrowables.EMPTY, false), 3, 1000L, AsyncDelay.NONE, new TestStopwatch()); CompletionStage result = retry.apply(new InvocationContext<>(() -> completedFuture("ignored"))); @@ -186,7 +185,7 @@ public void shouldRetryMaxTimesAndFail() { })); CompletionStageExecution execution = new CompletionStageExecution<>(invocation, executor); CompletionStageRetry retry = new CompletionStageRetry<>(execution, "shouldRetryMaxTimesAndSucceed", - SetOfThrowables.create(Collections.singletonList(RuntimeException.class)), SetOfThrowables.EMPTY, + new ExceptionDecision(SetOfThrowables.create(RuntimeException.class), SetOfThrowables.EMPTY, false), 3, 1000L, AsyncDelay.NONE, new TestStopwatch()); CompletionStage result = retry.apply(new InvocationContext<>(() -> completedFuture("ignored"))); diff --git a/implementation/core/src/test/java/io/smallrye/faulttolerance/core/retry/FutureRetryTest.java b/implementation/core/src/test/java/io/smallrye/faulttolerance/core/retry/FutureRetryTest.java index 2583e05c..e31b6500 100644 --- a/implementation/core/src/test/java/io/smallrye/faulttolerance/core/retry/FutureRetryTest.java +++ b/implementation/core/src/test/java/io/smallrye/faulttolerance/core/retry/FutureRetryTest.java @@ -6,13 +6,13 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import java.util.Collections; import java.util.concurrent.Future; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import io.smallrye.faulttolerance.core.stopwatch.TestStopwatch; +import io.smallrye.faulttolerance.core.util.ExceptionDecision; import io.smallrye.faulttolerance.core.util.SetOfThrowables; import io.smallrye.faulttolerance.core.util.TestException; import io.smallrye.faulttolerance.core.util.TestThread; @@ -22,8 +22,8 @@ * Replicates a subset of {@link RetryTest} because the underlying logic is the same. */ public class FutureRetryTest { - private static final SetOfThrowables exception = SetOfThrowables.create(Collections.singletonList(Exception.class)); - private static final SetOfThrowables testException = SetOfThrowables.create(Collections.singletonList(TestException.class)); + private static final SetOfThrowables exception = SetOfThrowables.create(Exception.class); + private static final SetOfThrowables testException = SetOfThrowables.create(TestException.class); private TestStopwatch stopwatch; @@ -37,7 +37,7 @@ public void immediatelyReturning_failedFuture() throws Exception { RuntimeException exception = new RuntimeException(); TestInvocation> invocation = TestInvocation.immediatelyReturning(() -> failedFuture(exception)); Retry> futureRetry = new Retry<>(invocation, "test invocation", - SetOfThrowables.EMPTY, SetOfThrowables.EMPTY, 3, 1000, SyncDelay.NONE, stopwatch); + ExceptionDecision.EMPTY, 3, 1000, SyncDelay.NONE, stopwatch); Future result = runOnTestThread(futureRetry).await(); assertThatThrownBy(result::get).hasCause(exception); assertThat(invocation.numberOfInvocations()).isEqualTo(1); @@ -47,7 +47,7 @@ public void immediatelyReturning_failedFuture() throws Exception { public void immediatelyReturning_value() throws Exception { TestInvocation> invocation = TestInvocation.immediatelyReturning(() -> completedFuture("foobar")); Future result = runOnTestThread( - new Retry<>(invocation, "test invocation", SetOfThrowables.EMPTY, SetOfThrowables.EMPTY, 3, 1000, + new Retry<>(invocation, "test invocation", ExceptionDecision.EMPTY, 3, 1000, SyncDelay.NONE, stopwatch)).await(); assertThat(result.get()).isEqualTo("foobar"); assertThat(invocation.numberOfInvocations()).isEqualTo(1); @@ -63,7 +63,7 @@ public void immediatelyReturning_interruptedInInvocation() throws InterruptedExc return completedFuture("foobar"); }); TestThread> executingThread = runOnTestThread(new Retry<>(invocation, "test invocation", - SetOfThrowables.EMPTY, SetOfThrowables.EMPTY, 3, 1000, SyncDelay.NONE, stopwatch)); + ExceptionDecision.EMPTY, 3, 1000, SyncDelay.NONE, stopwatch)); startInvocationBarrier.await(); executingThread.interrupt(); assertThatThrownBy(executingThread::await).isInstanceOf(InterruptedException.class); @@ -81,7 +81,7 @@ public void immediatelyReturning_selfInterruptedInInvocation() throws Interrupte throw new RuntimeException(); }); TestThread> executingThread = runOnTestThread(new Retry<>(invocation, "test invocation", - SetOfThrowables.EMPTY, SetOfThrowables.EMPTY, 3, 1000, SyncDelay.NONE, stopwatch)); + ExceptionDecision.EMPTY, 3, 1000, SyncDelay.NONE, stopwatch)); startInvocationBarrier.await(); endInvocationBarrier.open(); assertThatThrownBy(executingThread::await).isInstanceOf(InterruptedException.class); @@ -93,7 +93,8 @@ public void initiallyFailing_retriedExceptionThenValue_equalToMaxRetries() throw TestInvocation> invocation = TestInvocation.initiallyFailing(3, RuntimeException::new, () -> completedFuture("foobar")); TestThread> result = runOnTestThread(new Retry<>(invocation, "test invocation", - exception, SetOfThrowables.EMPTY, 3, 1000, SyncDelay.NONE, stopwatch)); + new ExceptionDecision(exception, SetOfThrowables.EMPTY, false), + 3, 1000, SyncDelay.NONE, stopwatch)); assertThat(result.await().get()).isEqualTo("foobar"); assertThat(invocation.numberOfInvocations()).isEqualTo(4); } @@ -103,7 +104,8 @@ public void initiallyFailing_retriedExceptionThenValue_moreThanMaxRetries() { TestInvocation> invocation = TestInvocation.initiallyFailing(4, RuntimeException::new, () -> completedFuture("foobar")); TestThread> result = runOnTestThread(new Retry<>(invocation, "test invocation", - exception, SetOfThrowables.EMPTY, 3, 1000, SyncDelay.NONE, stopwatch)); + new ExceptionDecision(exception, SetOfThrowables.EMPTY, false), + 3, 1000, SyncDelay.NONE, stopwatch)); assertThatThrownBy(result::await).isExactlyInstanceOf(RuntimeException.class); assertThat(invocation.numberOfInvocations()).isEqualTo(4); } @@ -113,7 +115,8 @@ public void initiallyFailing_retriedExceptionThenRetriedException_equalToMaxRetr TestInvocation> invocation = TestInvocation.initiallyFailing(3, RuntimeException::new, TestException::doThrow); TestThread> result = runOnTestThread(new Retry<>(invocation, "test invocation", - exception, SetOfThrowables.EMPTY, 3, 1000, SyncDelay.NONE, stopwatch)); + new ExceptionDecision(exception, SetOfThrowables.EMPTY, false), + 3, 1000, SyncDelay.NONE, stopwatch)); assertThatThrownBy(result::await).isExactlyInstanceOf(TestException.class); assertThat(invocation.numberOfInvocations()).isEqualTo(4); } @@ -128,7 +131,8 @@ public void initiallyFailing_retriedExceptionThenValue_interruptedInInvocation() return completedFuture("foobar"); }); TestThread> executingThread = runOnTestThread(new Retry<>(invocation, "test invocation", - exception, testException, 3, 1000, SyncDelay.NONE, stopwatch)); + new ExceptionDecision(exception, testException, false), + 3, 1000, SyncDelay.NONE, stopwatch)); startInvocationBarrier.await(); executingThread.interrupt(); assertThatThrownBy(executingThread::await).isInstanceOf(InterruptedException.class); diff --git a/implementation/core/src/test/java/io/smallrye/faulttolerance/core/retry/RetryTest.java b/implementation/core/src/test/java/io/smallrye/faulttolerance/core/retry/RetryTest.java index eeef9aa2..e0b50302 100644 --- a/implementation/core/src/test/java/io/smallrye/faulttolerance/core/retry/RetryTest.java +++ b/implementation/core/src/test/java/io/smallrye/faulttolerance/core/retry/RetryTest.java @@ -4,20 +4,19 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import java.util.Collections; - import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import io.smallrye.faulttolerance.core.stopwatch.TestStopwatch; +import io.smallrye.faulttolerance.core.util.ExceptionDecision; import io.smallrye.faulttolerance.core.util.SetOfThrowables; import io.smallrye.faulttolerance.core.util.TestException; import io.smallrye.faulttolerance.core.util.TestThread; import io.smallrye.faulttolerance.core.util.barrier.Barrier; public class RetryTest { - private static final SetOfThrowables exception = SetOfThrowables.create(Collections.singletonList(Exception.class)); - private static final SetOfThrowables testException = SetOfThrowables.create(Collections.singletonList(TestException.class)); + private static final SetOfThrowables exception = SetOfThrowables.create(Exception.class); + private static final SetOfThrowables testException = SetOfThrowables.create(TestException.class); private TestStopwatch stopwatch; @@ -30,7 +29,7 @@ public void setUp() { public void immediatelyReturning_value() throws Exception { TestInvocation invocation = TestInvocation.immediatelyReturning(() -> "foobar"); TestThread result = runOnTestThread(new Retry<>(invocation, "test invocation", - SetOfThrowables.EMPTY, SetOfThrowables.EMPTY, 3, 1000, SyncDelay.NONE, stopwatch)); + ExceptionDecision.EMPTY, 3, 1000, SyncDelay.NONE, stopwatch)); assertThat(result.await()).isEqualTo("foobar"); assertThat(invocation.numberOfInvocations()).isEqualTo(1); } @@ -39,7 +38,8 @@ public void immediatelyReturning_value() throws Exception { public void immediatelyReturning_retriedException() { TestInvocation invocation = TestInvocation.immediatelyReturning(TestException::doThrow); TestThread result = runOnTestThread(new Retry<>(invocation, "test invocation", - exception, SetOfThrowables.EMPTY, 3, 1000, SyncDelay.NONE, stopwatch)); + new ExceptionDecision(exception, SetOfThrowables.EMPTY, false), + 3, 1000, SyncDelay.NONE, stopwatch)); assertThatThrownBy(result::await).isExactlyInstanceOf(TestException.class); assertThat(invocation.numberOfInvocations()).isEqualTo(4); } @@ -48,7 +48,8 @@ public void immediatelyReturning_retriedException() { public void immediatelyReturning_abortingException() { TestInvocation invocation = TestInvocation.immediatelyReturning(TestException::doThrow); TestThread result = runOnTestThread(new Retry<>(invocation, "test invocation", - exception, testException, 3, 1000, SyncDelay.NONE, stopwatch)); + new ExceptionDecision(exception, testException, false), + 3, 1000, SyncDelay.NONE, stopwatch)); assertThatThrownBy(result::await).isExactlyInstanceOf(TestException.class); assertThat(invocation.numberOfInvocations()).isEqualTo(1); } @@ -57,7 +58,7 @@ public void immediatelyReturning_abortingException() { public void immediatelyReturning_unknownException() { TestInvocation invocation = TestInvocation.immediatelyReturning(TestException::doThrow); TestThread result = runOnTestThread(new Retry<>(invocation, "test invocation", - SetOfThrowables.EMPTY, SetOfThrowables.EMPTY, 3, 1000, SyncDelay.NONE, stopwatch)); + ExceptionDecision.EMPTY, 3, 1000, SyncDelay.NONE, stopwatch)); assertThatThrownBy(result::await).isExactlyInstanceOf(TestException.class); assertThat(invocation.numberOfInvocations()).isEqualTo(1); } @@ -72,7 +73,7 @@ public void immediatelyReturning_interruptedInInvocation() throws InterruptedExc return "foobar"; }); TestThread executingThread = runOnTestThread(new Retry<>(invocation, "test invocation", - SetOfThrowables.EMPTY, SetOfThrowables.EMPTY, 3, 1000, SyncDelay.NONE, stopwatch)); + ExceptionDecision.EMPTY, 3, 1000, SyncDelay.NONE, stopwatch)); startInvocationBarrier.await(); executingThread.interrupt(); assertThatThrownBy(executingThread::await).isInstanceOf(InterruptedException.class); @@ -90,7 +91,7 @@ public void immediatelyReturning_selfInterruptedInInvocation() throws Interrupte throw new RuntimeException(); }); TestThread executingThread = runOnTestThread(new Retry<>(invocation, "test invocation", - SetOfThrowables.EMPTY, SetOfThrowables.EMPTY, 3, 1000, SyncDelay.NONE, stopwatch)); + ExceptionDecision.EMPTY, 3, 1000, SyncDelay.NONE, stopwatch)); startInvocationBarrier.await(); endInvocationBarrier.open(); assertThatThrownBy(executingThread::await).isInstanceOf(InterruptedException.class); @@ -101,7 +102,8 @@ public void immediatelyReturning_selfInterruptedInInvocation() throws Interrupte public void initiallyFailing_retriedExceptionThenValue_lessThanMaxRetries() throws Exception { TestInvocation invocation = TestInvocation.initiallyFailing(2, RuntimeException::new, () -> "foobar"); TestThread result = runOnTestThread(new Retry<>(invocation, "test invocation", - exception, SetOfThrowables.EMPTY, 3, 1000, SyncDelay.NONE, stopwatch)); + new ExceptionDecision(exception, SetOfThrowables.EMPTY, false), + 3, 1000, SyncDelay.NONE, stopwatch)); assertThat(result.await()).isEqualTo("foobar"); assertThat(invocation.numberOfInvocations()).isEqualTo(3); } @@ -110,7 +112,8 @@ public void initiallyFailing_retriedExceptionThenValue_lessThanMaxRetries() thro public void initiallyFailing_retriedExceptionThenValue_equalToMaxRetries() throws Exception { TestInvocation invocation = TestInvocation.initiallyFailing(3, RuntimeException::new, () -> "foobar"); TestThread result = runOnTestThread(new Retry<>(invocation, "test invocation", - exception, SetOfThrowables.EMPTY, 3, 1000, SyncDelay.NONE, stopwatch)); + new ExceptionDecision(exception, SetOfThrowables.EMPTY, false), + 3, 1000, SyncDelay.NONE, stopwatch)); assertThat(result.await()).isEqualTo("foobar"); assertThat(invocation.numberOfInvocations()).isEqualTo(4); } @@ -119,7 +122,8 @@ public void initiallyFailing_retriedExceptionThenValue_equalToMaxRetries() throw public void initiallyFailing_retriedExceptionThenValue_moreThanMaxRetries() { TestInvocation invocation = TestInvocation.initiallyFailing(4, RuntimeException::new, () -> "foobar"); TestThread result = runOnTestThread(new Retry<>(invocation, "test invocation", - exception, SetOfThrowables.EMPTY, 3, 1000, SyncDelay.NONE, stopwatch)); + new ExceptionDecision(exception, SetOfThrowables.EMPTY, false), + 3, 1000, SyncDelay.NONE, stopwatch)); assertThatThrownBy(result::await).isExactlyInstanceOf(RuntimeException.class); assertThat(invocation.numberOfInvocations()).isEqualTo(4); } @@ -128,7 +132,8 @@ public void initiallyFailing_retriedExceptionThenValue_moreThanMaxRetries() { public void initiallyFailing_retriedExceptionThenRetriedException_lessThanMaxRetries() { TestInvocation invocation = TestInvocation.initiallyFailing(2, RuntimeException::new, TestException::doThrow); TestThread result = runOnTestThread(new Retry<>(invocation, "test invocation", - exception, SetOfThrowables.EMPTY, 3, 1000, SyncDelay.NONE, stopwatch)); + new ExceptionDecision(exception, SetOfThrowables.EMPTY, false), + 3, 1000, SyncDelay.NONE, stopwatch)); assertThatThrownBy(result::await).isExactlyInstanceOf(TestException.class); assertThat(invocation.numberOfInvocations()).isEqualTo(4); } @@ -137,7 +142,8 @@ public void initiallyFailing_retriedExceptionThenRetriedException_lessThanMaxRet public void initiallyFailing_retriedExceptionThenRetriedException_equalToMaxRetries() { TestInvocation invocation = TestInvocation.initiallyFailing(3, RuntimeException::new, TestException::doThrow); TestThread result = runOnTestThread(new Retry<>(invocation, "test invocation", - exception, SetOfThrowables.EMPTY, 3, 1000, SyncDelay.NONE, stopwatch)); + new ExceptionDecision(exception, SetOfThrowables.EMPTY, false), + 3, 1000, SyncDelay.NONE, stopwatch)); assertThatThrownBy(result::await).isExactlyInstanceOf(TestException.class); assertThat(invocation.numberOfInvocations()).isEqualTo(4); } @@ -146,7 +152,8 @@ public void initiallyFailing_retriedExceptionThenRetriedException_equalToMaxRetr public void initiallyFailing_retriedExceptionThenRetriedException_moreThanMaxRetries() { TestInvocation invocation = TestInvocation.initiallyFailing(4, RuntimeException::new, TestException::doThrow); TestThread result = runOnTestThread(new Retry<>(invocation, "test invocation", - exception, SetOfThrowables.EMPTY, 3, 1000, SyncDelay.NONE, stopwatch)); + new ExceptionDecision(exception, SetOfThrowables.EMPTY, false), + 3, 1000, SyncDelay.NONE, stopwatch)); assertThatThrownBy(result::await).isExactlyInstanceOf(RuntimeException.class); assertThat(invocation.numberOfInvocations()).isEqualTo(4); } @@ -155,7 +162,8 @@ public void initiallyFailing_retriedExceptionThenRetriedException_moreThanMaxRet public void initiallyFailing_retriedExceptionThenAbortingException_lessThanMaxRetries() { TestInvocation invocation = TestInvocation.initiallyFailing(2, RuntimeException::new, TestException::doThrow); TestThread result = runOnTestThread(new Retry<>(invocation, "test invocation", - exception, testException, 3, 1000, SyncDelay.NONE, stopwatch)); + new ExceptionDecision(exception, testException, false), + 3, 1000, SyncDelay.NONE, stopwatch)); assertThatThrownBy(result::await).isExactlyInstanceOf(TestException.class); assertThat(invocation.numberOfInvocations()).isEqualTo(3); } @@ -164,7 +172,8 @@ public void initiallyFailing_retriedExceptionThenAbortingException_lessThanMaxRe public void initiallyFailing_retriedExceptionThenAbortingException_equalToMaxRetries() { TestInvocation invocation = TestInvocation.initiallyFailing(3, RuntimeException::new, TestException::doThrow); TestThread result = runOnTestThread(new Retry<>(invocation, "test invocation", - exception, testException, 3, 1000, SyncDelay.NONE, stopwatch)); + new ExceptionDecision(exception, testException, false), + 3, 1000, SyncDelay.NONE, stopwatch)); assertThatThrownBy(result::await).isExactlyInstanceOf(TestException.class); assertThat(invocation.numberOfInvocations()).isEqualTo(4); } @@ -173,7 +182,8 @@ public void initiallyFailing_retriedExceptionThenAbortingException_equalToMaxRet public void initiallyFailing_retriedExceptionThenAbortingException_moreThanMaxRetries() { TestInvocation invocation = TestInvocation.initiallyFailing(4, RuntimeException::new, TestException::doThrow); TestThread result = runOnTestThread(new Retry<>(invocation, "test invocation", - exception, testException, 3, 1000, SyncDelay.NONE, stopwatch)); + new ExceptionDecision(exception, testException, false), + 3, 1000, SyncDelay.NONE, stopwatch)); assertThatThrownBy(result::await).isExactlyInstanceOf(RuntimeException.class); assertThat(invocation.numberOfInvocations()).isEqualTo(4); } @@ -185,7 +195,8 @@ public void initiallyFailing_retriedExceptionThenValue_totalDelayLessThanMaxDura TestDelay delay = TestDelay.normal(startDelayBarrier, endDelayBarrier); TestInvocation invocation = TestInvocation.initiallyFailing(3, RuntimeException::new, () -> "foobar"); TestThread result = runOnTestThread(new Retry<>(invocation, "test invocation", - exception, SetOfThrowables.EMPTY, 3, 1000, () -> delay, stopwatch)); + new ExceptionDecision(exception, SetOfThrowables.EMPTY, false), + 3, 1000, () -> delay, stopwatch)); startDelayBarrier.await(); stopwatch.setCurrentValue(500); endDelayBarrier.open(); @@ -200,7 +211,8 @@ public void initiallyFailing_retriedExceptionThenValue_totalDelayEqualToMaxDurat TestDelay delay = TestDelay.normal(startDelayBarrier, endDelayBarrier); TestInvocation invocation = TestInvocation.initiallyFailing(3, RuntimeException::new, () -> "foobar"); TestThread result = runOnTestThread(new Retry<>(invocation, "test invocation", - exception, SetOfThrowables.EMPTY, 3, 1000, () -> delay, stopwatch)); + new ExceptionDecision(exception, SetOfThrowables.EMPTY, false), + 3, 1000, () -> delay, stopwatch)); startDelayBarrier.await(); stopwatch.setCurrentValue(1000); endDelayBarrier.open(); @@ -215,7 +227,8 @@ public void initiallyFailing_retriedExceptionThenValue_totalDelayMoreThanMaxDura TestDelay delay = TestDelay.normal(startDelayBarrier, endDelayBarrier); TestInvocation invocation = TestInvocation.initiallyFailing(3, RuntimeException::new, () -> "foobar"); TestThread result = runOnTestThread(new Retry<>(invocation, "test invocation", - exception, SetOfThrowables.EMPTY, 3, 1000, () -> delay, stopwatch)); + new ExceptionDecision(exception, SetOfThrowables.EMPTY, false), + 3, 1000, () -> delay, stopwatch)); startDelayBarrier.await(); stopwatch.setCurrentValue(1500); endDelayBarrier.open(); @@ -231,7 +244,8 @@ public void initiallyFailing_retriedExceptionThenRetriedException_totalDelayLess TestDelay delay = TestDelay.normal(startDelayBarrier, endDelayBarrier); TestInvocation invocation = TestInvocation.initiallyFailing(3, RuntimeException::new, TestException::doThrow); TestThread result = runOnTestThread(new Retry<>(invocation, "test invocation", - exception, SetOfThrowables.EMPTY, 3, 1000, () -> delay, stopwatch)); + new ExceptionDecision(exception, SetOfThrowables.EMPTY, false), + 3, 1000, () -> delay, stopwatch)); startDelayBarrier.await(); stopwatch.setCurrentValue(500); endDelayBarrier.open(); @@ -247,7 +261,8 @@ public void initiallyFailing_retriedExceptionThenRetriedException_totalDelayEqua TestDelay delay = TestDelay.normal(startDelayBarrier, endDelayBarrier); TestInvocation invocation = TestInvocation.initiallyFailing(3, RuntimeException::new, TestException::doThrow); TestThread result = runOnTestThread(new Retry<>(invocation, "test invocation", - exception, SetOfThrowables.EMPTY, 3, 1000, () -> delay, stopwatch)); + new ExceptionDecision(exception, SetOfThrowables.EMPTY, false), + 3, 1000, () -> delay, stopwatch)); startDelayBarrier.await(); stopwatch.setCurrentValue(1000); endDelayBarrier.open(); @@ -263,7 +278,8 @@ public void initiallyFailing_retriedExceptionThenRetriedException_totalDelayMore TestDelay delay = TestDelay.normal(startDelayBarrier, endDelayBarrier); TestInvocation invocation = TestInvocation.initiallyFailing(3, RuntimeException::new, TestException::doThrow); TestThread result = runOnTestThread(new Retry<>(invocation, "test invocation", - exception, SetOfThrowables.EMPTY, 3, 1000, () -> delay, stopwatch)); + new ExceptionDecision(exception, SetOfThrowables.EMPTY, false), + 3, 1000, () -> delay, stopwatch)); startDelayBarrier.await(); stopwatch.setCurrentValue(1500); endDelayBarrier.open(); @@ -279,7 +295,8 @@ public void initiallyFailing_retriedExceptionThenAbortingException_totalDelayLes TestDelay delay = TestDelay.normal(startDelayBarrier, endDelayBarrier); TestInvocation invocation = TestInvocation.initiallyFailing(3, RuntimeException::new, TestException::doThrow); TestThread result = runOnTestThread(new Retry<>(invocation, "test invocation", - exception, testException, 3, 1000, () -> delay, stopwatch)); + new ExceptionDecision(exception, testException, false), + 3, 1000, () -> delay, stopwatch)); startDelayBarrier.await(); stopwatch.setCurrentValue(500); endDelayBarrier.open(); @@ -295,7 +312,8 @@ public void initiallyFailing_retriedExceptionThenAbortingException_totalDelayEqu TestDelay delay = TestDelay.normal(startDelayBarrier, endDelayBarrier); TestInvocation invocation = TestInvocation.initiallyFailing(3, RuntimeException::new, TestException::doThrow); TestThread result = runOnTestThread(new Retry<>(invocation, "test invocation", - exception, testException, 3, 1000, () -> delay, stopwatch)); + new ExceptionDecision(exception, testException, false), + 3, 1000, () -> delay, stopwatch)); startDelayBarrier.await(); stopwatch.setCurrentValue(1000); endDelayBarrier.open(); @@ -311,7 +329,8 @@ public void initiallyFailing_retriedExceptionThenAbortingException_totalDelayMor TestDelay delay = TestDelay.normal(startDelayBarrier, endDelayBarrier); TestInvocation invocation = TestInvocation.initiallyFailing(3, RuntimeException::new, TestException::doThrow); TestThread result = runOnTestThread(new Retry<>(invocation, "test invocation", - exception, testException, 3, 1000, () -> delay, stopwatch)); + new ExceptionDecision(exception, testException, false), + 3, 1000, () -> delay, stopwatch)); startDelayBarrier.await(); stopwatch.setCurrentValue(1500); endDelayBarrier.open(); @@ -323,7 +342,8 @@ public void initiallyFailing_retriedExceptionThenAbortingException_totalDelayMor public void initiallyFailing_retriedExceptionThenValue_infiniteRetries() throws Exception { TestInvocation invocation = TestInvocation.initiallyFailing(10, RuntimeException::new, () -> "foobar"); TestThread result = runOnTestThread(new Retry<>(invocation, "test invocation", - exception, SetOfThrowables.EMPTY, -1, 1000, SyncDelay.NONE, stopwatch)); + new ExceptionDecision(exception, SetOfThrowables.EMPTY, false), + -1, 1000, SyncDelay.NONE, stopwatch)); assertThat(result.await()).isEqualTo("foobar"); assertThat(invocation.numberOfInvocations()).isEqualTo(11); } @@ -332,7 +352,8 @@ public void initiallyFailing_retriedExceptionThenValue_infiniteRetries() throws public void initiallyFailing_retriedExceptionThenAbortingException_infiniteRetries() { TestInvocation invocation = TestInvocation.initiallyFailing(10, RuntimeException::new, TestException::doThrow); TestThread result = runOnTestThread(new Retry<>(invocation, "test invocation", - exception, testException, -1, 1000, SyncDelay.NONE, stopwatch)); + new ExceptionDecision(exception, testException, false), + -1, 1000, SyncDelay.NONE, stopwatch)); assertThatThrownBy(result::await).isExactlyInstanceOf(TestException.class); assertThat(invocation.numberOfInvocations()).isEqualTo(11); } @@ -344,7 +365,8 @@ public void initiallyFailing_retriedExceptionThenValue_infiniteDuration() throws TestDelay delay = TestDelay.normal(startDelayBarrier, endDelayBarrier); TestInvocation invocation = TestInvocation.initiallyFailing(3, RuntimeException::new, () -> "foobar"); TestThread result = runOnTestThread(new Retry<>(invocation, "test invocation", - exception, SetOfThrowables.EMPTY, 3, -1, () -> delay, stopwatch)); + new ExceptionDecision(exception, SetOfThrowables.EMPTY, false), + 3, -1, () -> delay, stopwatch)); startDelayBarrier.await(); stopwatch.setCurrentValue(1_000_000_000L); endDelayBarrier.open(); @@ -359,7 +381,8 @@ public void initiallyFailing_retriedExceptionThenRetriedException_infiniteDurati TestDelay delay = TestDelay.normal(startDelayBarrier, endDelayBarrier); TestInvocation invocation = TestInvocation.initiallyFailing(3, RuntimeException::new, TestException::doThrow); TestThread result = runOnTestThread(new Retry<>(invocation, "test invocation", - exception, SetOfThrowables.EMPTY, 3, -1, () -> delay, stopwatch)); + new ExceptionDecision(exception, SetOfThrowables.EMPTY, false), + 3, -1, () -> delay, stopwatch)); startDelayBarrier.await(); stopwatch.setCurrentValue(1_000_000_000L); endDelayBarrier.open(); @@ -374,7 +397,8 @@ public void initiallyFailing_retriedExceptionThenAbortingException_infiniteDurat TestDelay delay = TestDelay.normal(startDelayBarrier, endDelayBarrier); TestInvocation invocation = TestInvocation.initiallyFailing(3, RuntimeException::new, TestException::doThrow); TestThread result = runOnTestThread(new Retry<>(invocation, "test invocation", - exception, testException, 3, -1, () -> delay, stopwatch)); + new ExceptionDecision(exception, testException, false), + 3, -1, () -> delay, stopwatch)); startDelayBarrier.await(); stopwatch.setCurrentValue(1_000_000_000L); endDelayBarrier.open(); @@ -392,7 +416,8 @@ public void initiallyFailing_retriedExceptionThenValue_interruptedInInvocation() return "foobar"; }); TestThread executingThread = runOnTestThread(new Retry<>(invocation, "test invocation", - exception, testException, 3, 1000, SyncDelay.NONE, stopwatch)); + new ExceptionDecision(exception, testException, false), + 3, 1000, SyncDelay.NONE, stopwatch)); startInvocationBarrier.await(); executingThread.interrupt(); assertThatThrownBy(executingThread::await).isInstanceOf(InterruptedException.class); @@ -409,7 +434,8 @@ public void initiallyFailing_retriedExceptionThenRetriedException_interruptedInI throw new TestException(); }); TestThread executingThread = runOnTestThread(new Retry<>(invocation, "test invocation", - exception, SetOfThrowables.EMPTY, 3, 1000, SyncDelay.NONE, stopwatch)); + new ExceptionDecision(exception, SetOfThrowables.EMPTY, false), + 3, 1000, SyncDelay.NONE, stopwatch)); startInvocationBarrier.await(); executingThread.interrupt(); assertThatThrownBy(executingThread::await).isInstanceOf(InterruptedException.class); @@ -426,7 +452,8 @@ public void initiallyFailing_retriedExceptionThenAbortingException_interruptedIn throw new TestException(); }); TestThread executingThread = runOnTestThread(new Retry<>(invocation, "test invocation", - exception, testException, 3, 1000, SyncDelay.NONE, stopwatch)); + new ExceptionDecision(exception, testException, false), + 3, 1000, SyncDelay.NONE, stopwatch)); startInvocationBarrier.await(); executingThread.interrupt(); assertThatThrownBy(executingThread::await).isInstanceOf(InterruptedException.class); @@ -440,7 +467,8 @@ public void initiallyFailing_retriedExceptionThenValue_interruptedInDelay() thro TestDelay delay = TestDelay.normal(startDelayBarrier, endDelayBarrier); TestInvocation invocation = TestInvocation.initiallyFailing(3, RuntimeException::new, () -> "foobar"); TestThread executingThread = runOnTestThread(new Retry<>(invocation, "test invocation", - exception, testException, 3, 1000, () -> delay, stopwatch)); + new ExceptionDecision(exception, testException, false), + 3, 1000, () -> delay, stopwatch)); startDelayBarrier.await(); executingThread.interrupt(); assertThatThrownBy(executingThread::await).isInstanceOf(InterruptedException.class); @@ -454,7 +482,8 @@ public void initiallyFailing_retriedExceptionThenRetriedException_interruptedInD TestDelay delay = TestDelay.normal(startDelayBarrier, endDelayBarrier); TestInvocation invocation = TestInvocation.initiallyFailing(3, RuntimeException::new, TestException::doThrow); TestThread executingThread = runOnTestThread(new Retry<>(invocation, "test invocation", - exception, SetOfThrowables.EMPTY, 3, 1000, () -> delay, stopwatch)); + new ExceptionDecision(exception, SetOfThrowables.EMPTY, false), + 3, 1000, () -> delay, stopwatch)); startDelayBarrier.await(); executingThread.interrupt(); assertThatThrownBy(executingThread::await).isInstanceOf(InterruptedException.class); @@ -468,7 +497,8 @@ public void initiallyFailing_retriedExceptionThenAbortingException_interruptedIn TestDelay delay = TestDelay.normal(startDelayBarrier, endDelayBarrier); TestInvocation invocation = TestInvocation.initiallyFailing(3, RuntimeException::new, TestException::doThrow); TestThread executingThread = runOnTestThread(new Retry<>(invocation, "test invocation", - exception, testException, 3, 1000, () -> delay, stopwatch)); + new ExceptionDecision(exception, testException, false), + 3, 1000, () -> delay, stopwatch)); startDelayBarrier.await(); executingThread.interrupt(); assertThatThrownBy(executingThread::await).isInstanceOf(InterruptedException.class); @@ -482,7 +512,8 @@ public void initiallyFailing_retriedExceptionThenValue_unexpectedExceptionInDela TestDelay delay = TestDelay.exceptionThrowing(startDelayBarrier, endDelayBarrier, RuntimeException::new); TestInvocation invocation = TestInvocation.initiallyFailing(3, RuntimeException::new, () -> "foobar"); TestThread executingThread = runOnTestThread(new Retry<>(invocation, "test invocation", - exception, testException, 3, 1000, () -> delay, stopwatch)); + new ExceptionDecision(exception, testException, false), + 3, 1000, () -> delay, stopwatch)); startDelayBarrier.await(); endDelayBarrier.open(); assertThatThrownBy(executingThread::await).isInstanceOf(RuntimeException.class); @@ -496,7 +527,8 @@ public void initiallyFailing_retriedExceptionThenRetriedException_unexpectedExce TestDelay delay = TestDelay.exceptionThrowing(startDelayBarrier, endDelayBarrier, RuntimeException::new); TestInvocation invocation = TestInvocation.initiallyFailing(3, RuntimeException::new, TestException::doThrow); TestThread executingThread = runOnTestThread(new Retry<>(invocation, "test invocation", - exception, SetOfThrowables.EMPTY, 3, 1000, () -> delay, stopwatch)); + new ExceptionDecision(exception, SetOfThrowables.EMPTY, false), + 3, 1000, () -> delay, stopwatch)); startDelayBarrier.await(); endDelayBarrier.open(); assertThatThrownBy(executingThread::await).isInstanceOf(RuntimeException.class); @@ -511,7 +543,8 @@ public void initiallyFailing_retriedExceptionThenAbortingException_unexpectedExc TestDelay delay = TestDelay.exceptionThrowing(startDelayBarrier, endDelayBarrier, RuntimeException::new); TestInvocation invocation = TestInvocation.initiallyFailing(3, RuntimeException::new, TestException::doThrow); TestThread executingThread = runOnTestThread(new Retry<>(invocation, "test invocation", - exception, testException, 3, 1000, () -> delay, stopwatch)); + new ExceptionDecision(exception, testException, false), + 3, 1000, () -> delay, stopwatch)); startDelayBarrier.await(); endDelayBarrier.open(); assertThatThrownBy(executingThread::await).isInstanceOf(RuntimeException.class); @@ -529,7 +562,8 @@ public void initiallyFailing_retriedExceptionThenSelfInterrupt() throws Interrup throw new RuntimeException(); }); TestThread executingThread = runOnTestThread(new Retry<>(invocation, "test invocation", - exception, SetOfThrowables.EMPTY, 3, 1000, SyncDelay.NONE, stopwatch)); + new ExceptionDecision(exception, SetOfThrowables.EMPTY, false), + 3, 1000, SyncDelay.NONE, stopwatch)); startInvocationBarrier.await(); endInvocationBarrier.open(); assertThatThrownBy(executingThread::await).isInstanceOf(InterruptedException.class); @@ -543,7 +577,8 @@ public void initiallyFailing_retriedExceptionThenValue_selfInterruptedInDelay() TestDelay delay = TestDelay.selfInterrupting(startDelayBarrier, endDelayBarrier); TestInvocation invocation = TestInvocation.initiallyFailing(3, RuntimeException::new, () -> "foobar"); TestThread executingThread = runOnTestThread(new Retry<>(invocation, "test invocation", - exception, testException, 3, 1000, () -> delay, stopwatch)); + new ExceptionDecision(exception, testException, false), + 3, 1000, () -> delay, stopwatch)); startDelayBarrier.await(); endDelayBarrier.open(); assertThatThrownBy(executingThread::await).isInstanceOf(InterruptedException.class); @@ -557,7 +592,8 @@ public void initiallyFailing_retriedExceptionThenRetriedException_selfInterrupte TestDelay delay = TestDelay.selfInterrupting(startDelayBarrier, endDelayBarrier); TestInvocation invocation = TestInvocation.initiallyFailing(3, RuntimeException::new, TestException::doThrow); TestThread executingThread = runOnTestThread(new Retry<>(invocation, "test invocation", - exception, SetOfThrowables.EMPTY, 3, 1000, () -> delay, stopwatch)); + new ExceptionDecision(exception, SetOfThrowables.EMPTY, false), + 3, 1000, () -> delay, stopwatch)); startDelayBarrier.await(); endDelayBarrier.open(); assertThatThrownBy(executingThread::await).isInstanceOf(InterruptedException.class); @@ -571,7 +607,8 @@ public void initiallyFailing_retriedExceptionThenAbortingException_selfInterrupt TestDelay delay = TestDelay.selfInterrupting(startDelayBarrier, endDelayBarrier); TestInvocation invocation = TestInvocation.initiallyFailing(3, RuntimeException::new, TestException::doThrow); TestThread executingThread = runOnTestThread(new Retry<>(invocation, "test invocation", - exception, testException, 3, 1000, () -> delay, stopwatch)); + new ExceptionDecision(exception, testException, false), + 3, 1000, () -> delay, stopwatch)); startDelayBarrier.await(); endDelayBarrier.open(); assertThatThrownBy(executingThread::await).isInstanceOf(InterruptedException.class); diff --git a/implementation/core/src/test/java/io/smallrye/faulttolerance/core/util/ExceptionDecisionTest.java b/implementation/core/src/test/java/io/smallrye/faulttolerance/core/util/ExceptionDecisionTest.java new file mode 100644 index 00000000..c1b65897 --- /dev/null +++ b/implementation/core/src/test/java/io/smallrye/faulttolerance/core/util/ExceptionDecisionTest.java @@ -0,0 +1,45 @@ +package io.smallrye.faulttolerance.core.util; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.Test; + +public class ExceptionDecisionTest { + @Test + public void consideredFailure() { + assertThat(ExceptionDecision.ALWAYS_FAILURE.isConsideredExpected(new Exception())).isFalse(); + } + + @Test + public void consideredExpected() { + assertThat(ExceptionDecision.ALWAYS_EXPECTED.isConsideredExpected(new Exception())).isTrue(); + } + + @Test + public void unknown() { + assertThat(ExceptionDecision.EMPTY.isConsideredExpected(new Exception())).isTrue(); + } + + @Test + public void causeConsideredFailure() { + ExceptionDecision decision = new ExceptionDecision(SetOfThrowables.create(TestException.class), + SetOfThrowables.EMPTY, true); + + assertThat(decision.isConsideredExpected(new Exception(new TestException()))).isFalse(); + } + + @Test + public void causeConsideredExpected() { + ExceptionDecision decision = new ExceptionDecision(SetOfThrowables.EMPTY, + SetOfThrowables.create(TestException.class), true); + + assertThat(decision.isConsideredExpected(new Exception(new TestException()))).isTrue(); + } + + @Test + public void causeUnknown() { + ExceptionDecision decision = new ExceptionDecision(SetOfThrowables.EMPTY, SetOfThrowables.EMPTY, true); + + assertThat(decision.isConsideredExpected(new Exception(new TestException()))).isTrue(); + } +} diff --git a/implementation/core/src/test/java/io/smallrye/faulttolerance/core/util/SetOfThrowablesTest.java b/implementation/core/src/test/java/io/smallrye/faulttolerance/core/util/SetOfThrowablesTest.java index 523ca46e..63bdae2a 100644 --- a/implementation/core/src/test/java/io/smallrye/faulttolerance/core/util/SetOfThrowablesTest.java +++ b/implementation/core/src/test/java/io/smallrye/faulttolerance/core/util/SetOfThrowablesTest.java @@ -3,14 +3,13 @@ import static org.assertj.core.api.Assertions.assertThat; import java.util.Arrays; -import java.util.Collections; import org.junit.jupiter.api.Test; public class SetOfThrowablesTest { @Test public void emptySet() { - SetOfThrowables set = SetOfThrowables.create(Collections.emptyList()); + SetOfThrowables set = SetOfThrowables.EMPTY; assertThat(set.includes(Throwable.class)).isFalse(); assertThat(set.includes(Exception.class)).isFalse(); @@ -20,7 +19,7 @@ public void emptySet() { @Test public void singletonSet_throwable() { - SetOfThrowables set = SetOfThrowables.create(Collections.singletonList(Throwable.class)); + SetOfThrowables set = SetOfThrowables.ALL; assertThat(set.includes(Throwable.class)).isTrue(); assertThat(set.includes(Exception.class)).isTrue(); @@ -30,7 +29,7 @@ public void singletonSet_throwable() { @Test public void singletonSet_exception() { - SetOfThrowables set = SetOfThrowables.create(Collections.singletonList(Exception.class)); + SetOfThrowables set = SetOfThrowables.create(Exception.class); assertThat(set.includes(Throwable.class)).isFalse(); assertThat(set.includes(Exception.class)).isTrue(); @@ -38,6 +37,16 @@ public void singletonSet_exception() { assertThat(set.includes(Error.class)).isFalse(); } + @Test + public void singletonSet_runtimeException() { + SetOfThrowables set = SetOfThrowables.create(RuntimeException.class); + + assertThat(set.includes(Throwable.class)).isFalse(); + assertThat(set.includes(Exception.class)).isFalse(); + assertThat(set.includes(RuntimeException.class)).isTrue(); + assertThat(set.includes(Error.class)).isFalse(); + } + @Test public void twoElements_throwableAndException() { SetOfThrowables set = SetOfThrowables.create(Arrays.asList(Throwable.class, Exception.class)); @@ -48,6 +57,16 @@ public void twoElements_throwableAndException() { assertThat(set.includes(Error.class)).isTrue(); } + @Test + public void twoElements_exceptionAndError() { + SetOfThrowables set = SetOfThrowables.create(Arrays.asList(Exception.class, Error.class)); + + assertThat(set.includes(Throwable.class)).isFalse(); + assertThat(set.includes(Exception.class)).isTrue(); + assertThat(set.includes(RuntimeException.class)).isTrue(); + assertThat(set.includes(Error.class)).isTrue(); + } + @Test public void twoElements_runtimeExceptionAndError() { SetOfThrowables set = SetOfThrowables.create(Arrays.asList(RuntimeException.class, Error.class)); 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 71997b3d..334731bc 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 @@ -82,6 +82,7 @@ import io.smallrye.faulttolerance.core.timeout.TimerTimeoutWatcher; import io.smallrye.faulttolerance.core.timer.Timer; import io.smallrye.faulttolerance.core.util.DirectExecutor; +import io.smallrye.faulttolerance.core.util.ExceptionDecision; import io.smallrye.faulttolerance.core.util.SetOfThrowables; import io.smallrye.faulttolerance.internal.AsyncTypesConversion; import io.smallrye.faulttolerance.internal.InterceptionPoint; @@ -249,8 +250,7 @@ private FaultToleranceStrategy> prepareComplectionStageCh if (operation.hasCircuitBreaker()) { long delayInMillis = getTimeInMs(operation.getCircuitBreaker().delay(), operation.getCircuitBreaker().delayUnit()); result = new CompletionStageCircuitBreaker<>(result, "CircuitBreaker[" + point + "]", - getSetOfThrowables(operation.getCircuitBreaker().failOn()), - getSetOfThrowables(operation.getCircuitBreaker().skipOn()), + createExceptionDecision(operation.getCircuitBreaker().skipOn(), operation.getCircuitBreaker().failOn()), delayInMillis, operation.getCircuitBreaker().requestVolumeThreshold(), operation.getCircuitBreaker().failureRatio(), @@ -270,8 +270,7 @@ private FaultToleranceStrategy> prepareComplectionStageCh result = new CompletionStageRetry<>(result, "Retry[" + point + "]", - getSetOfThrowables(operation.getRetry().retryOn()), - getSetOfThrowables(operation.getRetry().abortOn()), + createExceptionDecision(operation.getRetry().abortOn(), operation.getRetry().retryOn()), operation.getRetry().maxRetries(), maxDurationMs, () -> new TimerDelay(backoff.get(), timer), @@ -283,8 +282,7 @@ private FaultToleranceStrategy> prepareComplectionStageCh result, "Fallback[" + point + "]", prepareFallbackFunction(point, operation), - getSetOfThrowables(operation.getFallback().applyOn()), - getSetOfThrowables(operation.getFallback().skipOn())); + createExceptionDecision(operation.getFallback().skipOn(), operation.getFallback().applyOn())); } if (metricsProvider.isEnabled()) { @@ -317,8 +315,7 @@ private FaultToleranceStrategy prepareSyncStrategy(FaultToleranceOperatio if (operation.hasCircuitBreaker()) { long delayInMillis = getTimeInMs(operation.getCircuitBreaker().delay(), operation.getCircuitBreaker().delayUnit()); result = new CircuitBreaker<>(result, "CircuitBreaker[" + point + "]", - getSetOfThrowables(operation.getCircuitBreaker().failOn()), - getSetOfThrowables(operation.getCircuitBreaker().skipOn()), + createExceptionDecision(operation.getCircuitBreaker().skipOn(), operation.getCircuitBreaker().failOn()), delayInMillis, operation.getCircuitBreaker().requestVolumeThreshold(), operation.getCircuitBreaker().failureRatio(), @@ -338,8 +335,7 @@ private FaultToleranceStrategy prepareSyncStrategy(FaultToleranceOperatio result = new Retry<>(result, "Retry[" + point + "]", - getSetOfThrowables(operation.getRetry().retryOn()), - getSetOfThrowables(operation.getRetry().abortOn()), + createExceptionDecision(operation.getRetry().abortOn(), operation.getRetry().retryOn()), operation.getRetry().maxRetries(), maxDurationMs, () -> new ThreadSleepDelay(backoff.get()), @@ -351,8 +347,7 @@ private FaultToleranceStrategy prepareSyncStrategy(FaultToleranceOperatio result, "Fallback[" + point + "]", prepareFallbackFunction(point, operation), - getSetOfThrowables(operation.getFallback().applyOn()), - getSetOfThrowables(operation.getFallback().skipOn())); + createExceptionDecision(operation.getFallback().skipOn(), operation.getFallback().applyOn())); } if (metricsProvider.isEnabled()) { @@ -386,8 +381,7 @@ private FaultToleranceStrategy> prepareFutureStrategy(FaultToleran if (operation.hasCircuitBreaker()) { long delayInMillis = getTimeInMs(operation.getCircuitBreaker().delay(), operation.getCircuitBreaker().delayUnit()); result = new CircuitBreaker<>(result, "CircuitBreaker[" + point + "]", - getSetOfThrowables(operation.getCircuitBreaker().failOn()), - getSetOfThrowables(operation.getCircuitBreaker().skipOn()), + createExceptionDecision(operation.getCircuitBreaker().skipOn(), operation.getCircuitBreaker().failOn()), delayInMillis, operation.getCircuitBreaker().requestVolumeThreshold(), operation.getCircuitBreaker().failureRatio(), @@ -407,8 +401,7 @@ private FaultToleranceStrategy> prepareFutureStrategy(FaultToleran result = new Retry<>(result, "Retry[" + point + "]", - getSetOfThrowables(operation.getRetry().retryOn()), - getSetOfThrowables(operation.getRetry().abortOn()), + createExceptionDecision(operation.getRetry().abortOn(), operation.getRetry().retryOn()), operation.getRetry().maxRetries(), maxDurationMs, () -> new ThreadSleepDelay(backoff.get()), @@ -419,8 +412,7 @@ private FaultToleranceStrategy> prepareFutureStrategy(FaultToleran result = new Fallback<>(result, "Fallback[" + point + "]", prepareFallbackFunction(point, operation), - getSetOfThrowables(operation.getFallback().applyOn()), - getSetOfThrowables(operation.getFallback().skipOn())); + createExceptionDecision(operation.getFallback().skipOn(), operation.getFallback().applyOn())); } if (metricsProvider.isEnabled()) { @@ -546,7 +538,13 @@ private long getTimeInMs(long time, ChronoUnit unit) { return Duration.of(time, unit).toMillis(); } - private SetOfThrowables getSetOfThrowables(Class[] throwableClasses) { + private ExceptionDecision createExceptionDecision(Class[] consideredExpected, + Class[] consideredFailure) { + return new ExceptionDecision(createSetOfThrowables(consideredFailure), + createSetOfThrowables(consideredExpected), specCompatibility.inspectExceptionCauseChain()); + } + + private SetOfThrowables createSetOfThrowables(Class[] throwableClasses) { if (throwableClasses == null) { return SetOfThrowables.EMPTY; } diff --git a/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/SpecCompatibility.java b/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/SpecCompatibility.java index 5b51c09e..fd4ce26e 100644 --- a/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/SpecCompatibility.java +++ b/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/SpecCompatibility.java @@ -40,4 +40,8 @@ public boolean isOperationPseudoAsynchronous(FaultToleranceOperation operation) public boolean isOperationTrulyOrPseudoAsynchronous(FaultToleranceOperation operation) { return isOperationTrulyAsynchronous(operation) || isOperationPseudoAsynchronous(operation); } + + public boolean inspectExceptionCauseChain() { + return !compatible; + } } diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/fallback/causechain/ExpectedOutcomeException.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/fallback/causechain/ExpectedOutcomeException.java new file mode 100644 index 00000000..d050783e --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/fallback/causechain/ExpectedOutcomeException.java @@ -0,0 +1,10 @@ +package io.smallrye.faulttolerance.fallback.causechain; + +public class ExpectedOutcomeException extends Exception { + public ExpectedOutcomeException() { + } + + public ExpectedOutcomeException(Throwable cause) { + super(cause); + } +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/fallback/causechain/FallbackWithExceptionCauseChainTest.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/fallback/causechain/FallbackWithExceptionCauseChainTest.java new file mode 100644 index 00000000..789a4119 --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/fallback/causechain/FallbackWithExceptionCauseChainTest.java @@ -0,0 +1,36 @@ +package io.smallrye.faulttolerance.fallback.causechain; + +import static org.assertj.core.api.Assertions.assertThatCode; + +import java.io.IOException; + +import org.junit.jupiter.api.Test; +import org.junitpioneer.jupiter.SetSystemProperty; + +import io.smallrye.faulttolerance.util.FaultToleranceBasicTest; + +@SetSystemProperty(key = "smallrye.faulttolerance.mp-compatibility", value = "false") +@FaultToleranceBasicTest +public class FallbackWithExceptionCauseChainTest { + @Test + public void test(MyService bean) { + assertThatCode(() -> bean.hello(new RuntimeException())).isExactlyInstanceOf(RuntimeException.class); + assertThatCode(() -> bean.hello(new RuntimeException(new IOException()))).doesNotThrowAnyException(); + assertThatCode(() -> bean.hello(new RuntimeException(new ExpectedOutcomeException()))) + .isExactlyInstanceOf(RuntimeException.class); + + assertThatCode(() -> bean.hello(new Exception())).isExactlyInstanceOf(Exception.class); + assertThatCode(() -> bean.hello(new Exception(new IOException()))).doesNotThrowAnyException(); + assertThatCode(() -> bean.hello(new Exception(new ExpectedOutcomeException()))).isExactlyInstanceOf(Exception.class); + + assertThatCode(() -> bean.hello(new IOException())).doesNotThrowAnyException(); + assertThatCode(() -> bean.hello(new IOException(new Exception()))).doesNotThrowAnyException(); + assertThatCode(() -> bean.hello(new IOException(new ExpectedOutcomeException()))).doesNotThrowAnyException(); + + assertThatCode(() -> bean.hello(new ExpectedOutcomeException())).isExactlyInstanceOf(ExpectedOutcomeException.class); + assertThatCode(() -> bean.hello(new ExpectedOutcomeException(new Exception()))) + .isExactlyInstanceOf(ExpectedOutcomeException.class); + assertThatCode(() -> bean.hello(new ExpectedOutcomeException(new IOException()))) + .isExactlyInstanceOf(ExpectedOutcomeException.class); + } +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/fallback/causechain/MyService.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/fallback/causechain/MyService.java new file mode 100644 index 00000000..e075c31a --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/fallback/causechain/MyService.java @@ -0,0 +1,18 @@ +package io.smallrye.faulttolerance.fallback.causechain; + +import java.io.IOException; + +import javax.enterprise.context.ApplicationScoped; + +import org.eclipse.microprofile.faulttolerance.Fallback; + +@ApplicationScoped +public class MyService { + @Fallback(fallbackMethod = "fallback", skipOn = ExpectedOutcomeException.class, applyOn = IOException.class) + public void hello(Exception e) throws Exception { + throw e; + } + + public void fallback(Exception ignored) { + } +} From 6ede407cad240a5b166be411663df02e7e95d0d1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 18 Jan 2022 07:10:16 +0000 Subject: [PATCH 04/68] Bump smallrye-config from 2.8.1 to 2.8.2 Bumps [smallrye-config](https://github.com/smallrye/smallrye-config) from 2.8.1 to 2.8.2. - [Release notes](https://github.com/smallrye/smallrye-config/releases) - [Commits](https://github.com/smallrye/smallrye-config/compare/2.8.1...2.8.2) --- updated-dependencies: - dependency-name: io.smallrye.config:smallrye-config dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 69b70fce..f3a830f1 100644 --- a/pom.xml +++ b/pom.xml @@ -42,7 +42,7 @@ 3.0 1.2 - 2.8.1 + 2.8.2 3.0.4 From 2d1fdeba0466c1015357e55d48a9911f7f7a7f84 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 20 Jan 2022 07:09:45 +0000 Subject: [PATCH 05/68] Bump mutiny from 1.3.0 to 1.3.1 Bumps [mutiny](https://github.com/smallrye/smallrye-mutiny) from 1.3.0 to 1.3.1. - [Release notes](https://github.com/smallrye/smallrye-mutiny/releases) - [Commits](https://github.com/smallrye/smallrye-mutiny/compare/1.3.0...1.3.1) --- updated-dependencies: - dependency-name: io.smallrye.reactive:mutiny dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index f3a830f1..fe4c80d2 100644 --- a/pom.xml +++ b/pom.xml @@ -58,7 +58,7 @@ 4.1.1 5.8.2 1.5.0 - 1.3.0 + 1.3.1 3.1.3 6.14.3 3.1.SP4 From 3f1cdbb88b8b0ea9227ee15f41fcb3ddc62cba8b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 20 Jan 2022 07:57:23 +0000 Subject: [PATCH 06/68] Bump vertx-core from 4.2.3 to 4.2.4 Bumps [vertx-core](https://github.com/eclipse/vert.x) from 4.2.3 to 4.2.4. - [Release notes](https://github.com/eclipse/vert.x/releases) - [Commits](https://github.com/eclipse/vert.x/compare/4.2.3...4.2.4) --- updated-dependencies: - dependency-name: io.vertx:vertx-core dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index fe4c80d2..3d0d0d7f 100644 --- a/pom.xml +++ b/pom.xml @@ -50,7 +50,7 @@ 1.8.0 2.6.0 0.33.0 - 4.2.3 + 4.2.4 1.6.0.Final 2.1.0.Final From dd81586e58ee91c72f3f55df4750ea832784f1df Mon Sep 17 00:00:00 2001 From: Ladislav Thon Date: Fri, 21 Jan 2022 09:33:58 +0100 Subject: [PATCH 07/68] initial version of programmatic API Includes two implementations: a CDI-integrated one and a completely standalone one. Documentation is included too. --- api/pom.xml | 4 + .../faulttolerance/api/FaultTolerance.java | 800 ++++++++++++++++++ .../faulttolerance/api/FaultToleranceSpi.java | 24 + .../api/FaultToleranceSpiAccess.java | 43 + doc/modules/ROOT/nav.adoc | 1 + .../ROOT/pages/integration/event-loop.adoc | 2 +- doc/modules/ROOT/pages/integration/intro.adoc | 10 +- .../ROOT/pages/usage/programmatic-api.adoc | 282 ++++++ implementation/core/pom.xml | 12 +- .../core/util/SetOfThrowables.java | 4 +- .../faulttolerance/core/util/Timing.java | 14 + .../faulttolerance/CdiFaultTolerance.java | 626 ++++++++++++++ .../faulttolerance/CdiFaultToleranceSpi.java | 84 ++ .../FaultToleranceExtension.java | 2 + ...llrye.faulttolerance.api.FaultToleranceSpi | 1 + implementation/pom.xml | 2 + implementation/standalone/pom.xml | 94 ++ .../StandaloneCircuitBreakerMaintenance.java | 52 ++ .../standalone/StandaloneFaultTolerance.java | 626 ++++++++++++++ .../StandaloneFaultToleranceSpi.java | 59 ++ ...llrye.faulttolerance.api.FaultToleranceSpi | 1 + .../test/StandaloneBulkheadAsyncTest.java | 45 + .../test/StandaloneBulkheadTest.java | 59 ++ .../StandaloneCircuitBreakerAsyncTest.java | 91 ++ .../test/StandaloneCircuitBreakerTest.java | 70 ++ .../test/StandaloneFallbackAsyncTest.java | 85 ++ .../test/StandaloneFallbackTest.java | 57 ++ .../test/StandalonePassthroughAsyncTest.java | 146 ++++ .../test/StandalonePassthroughTest.java | 116 +++ .../test/StandaloneRetryAsyncTest.java | 90 ++ .../standalone/test/StandaloneRetryTest.java | 62 ++ .../test/StandaloneTimeoutAsyncTest.java | 43 + .../test/StandaloneTimeoutTest.java | 37 + .../src/test/resources/logging.properties | 5 + pom.xml | 11 + testsuite/basic/pom.xml | 14 +- .../programmatic/CdiBulkheadAsyncTest.java | 8 + .../programmatic/CdiBulkheadTest.java | 8 + .../CdiCircuitBreakerAsyncTest.java | 8 + .../programmatic/CdiCircuitBreakerTest.java | 8 + .../programmatic/CdiFallbackAsyncTest.java | 8 + .../programmatic/CdiFallbackTest.java | 8 + .../programmatic/CdiPassthroughAsyncTest.java | 8 + .../programmatic/CdiPassthroughTest.java | 8 + .../programmatic/CdiRetryAsyncTest.java | 8 + .../programmatic/CdiRetryTest.java | 8 + .../CdiSkipFaultToleranceTest.java | 46 + .../programmatic/CdiTimeoutAsyncTest.java | 8 + .../programmatic/CdiTimeoutTest.java | 8 + 49 files changed, 3803 insertions(+), 13 deletions(-) create mode 100644 api/src/main/java/io/smallrye/faulttolerance/api/FaultTolerance.java create mode 100644 api/src/main/java/io/smallrye/faulttolerance/api/FaultToleranceSpi.java create mode 100644 api/src/main/java/io/smallrye/faulttolerance/api/FaultToleranceSpiAccess.java create mode 100644 doc/modules/ROOT/pages/usage/programmatic-api.adoc create mode 100644 implementation/core/src/test/java/io/smallrye/faulttolerance/core/util/Timing.java create mode 100644 implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/CdiFaultTolerance.java create mode 100644 implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/CdiFaultToleranceSpi.java create mode 100644 implementation/fault-tolerance/src/main/resources/META-INF/services/io.smallrye.faulttolerance.api.FaultToleranceSpi create mode 100644 implementation/standalone/pom.xml create mode 100644 implementation/standalone/src/main/java/io/smallrye/faulttolerance/standalone/StandaloneCircuitBreakerMaintenance.java create mode 100644 implementation/standalone/src/main/java/io/smallrye/faulttolerance/standalone/StandaloneFaultTolerance.java create mode 100644 implementation/standalone/src/main/java/io/smallrye/faulttolerance/standalone/StandaloneFaultToleranceSpi.java create mode 100644 implementation/standalone/src/main/resources/META-INF/services/io.smallrye.faulttolerance.api.FaultToleranceSpi create mode 100644 implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneBulkheadAsyncTest.java create mode 100644 implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneBulkheadTest.java create mode 100644 implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneCircuitBreakerAsyncTest.java create mode 100644 implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneCircuitBreakerTest.java create mode 100644 implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneFallbackAsyncTest.java create mode 100644 implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneFallbackTest.java create mode 100644 implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandalonePassthroughAsyncTest.java create mode 100644 implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandalonePassthroughTest.java create mode 100644 implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneRetryAsyncTest.java create mode 100644 implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneRetryTest.java create mode 100644 implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneTimeoutAsyncTest.java create mode 100644 implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneTimeoutTest.java create mode 100644 implementation/standalone/src/test/resources/logging.properties create mode 100644 testsuite/basic/src/test/java/io/smallrye/faulttolerance/programmatic/CdiBulkheadAsyncTest.java create mode 100644 testsuite/basic/src/test/java/io/smallrye/faulttolerance/programmatic/CdiBulkheadTest.java create mode 100644 testsuite/basic/src/test/java/io/smallrye/faulttolerance/programmatic/CdiCircuitBreakerAsyncTest.java create mode 100644 testsuite/basic/src/test/java/io/smallrye/faulttolerance/programmatic/CdiCircuitBreakerTest.java create mode 100644 testsuite/basic/src/test/java/io/smallrye/faulttolerance/programmatic/CdiFallbackAsyncTest.java create mode 100644 testsuite/basic/src/test/java/io/smallrye/faulttolerance/programmatic/CdiFallbackTest.java create mode 100644 testsuite/basic/src/test/java/io/smallrye/faulttolerance/programmatic/CdiPassthroughAsyncTest.java create mode 100644 testsuite/basic/src/test/java/io/smallrye/faulttolerance/programmatic/CdiPassthroughTest.java create mode 100644 testsuite/basic/src/test/java/io/smallrye/faulttolerance/programmatic/CdiRetryAsyncTest.java create mode 100644 testsuite/basic/src/test/java/io/smallrye/faulttolerance/programmatic/CdiRetryTest.java create mode 100644 testsuite/basic/src/test/java/io/smallrye/faulttolerance/programmatic/CdiSkipFaultToleranceTest.java create mode 100644 testsuite/basic/src/test/java/io/smallrye/faulttolerance/programmatic/CdiTimeoutAsyncTest.java create mode 100644 testsuite/basic/src/test/java/io/smallrye/faulttolerance/programmatic/CdiTimeoutTest.java diff --git a/api/pom.xml b/api/pom.xml index 4adbe64d..395c2109 100644 --- a/api/pom.xml +++ b/api/pom.xml @@ -32,6 +32,10 @@ jakarta.enterprise jakarta.enterprise.cdi-api + + org.eclipse.microprofile.fault-tolerance + microprofile-fault-tolerance-api + io.smallrye.common smallrye-common-annotation diff --git a/api/src/main/java/io/smallrye/faulttolerance/api/FaultTolerance.java b/api/src/main/java/io/smallrye/faulttolerance/api/FaultTolerance.java new file mode 100644 index 00000000..ab55dd0a --- /dev/null +++ b/api/src/main/java/io/smallrye/faulttolerance/api/FaultTolerance.java @@ -0,0 +1,800 @@ +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.CompletionStage; +import java.util.concurrent.Future; +import java.util.function.Consumer; +import java.util.function.Function; +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 io.smallrye.common.annotation.Experimental; + +/** + * Allows guarding an action with various fault tolerance strategies: bulkhead, circuit breaker, fallback, 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 ➤ 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 circuit breakers and bulkheads 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 circuit breaker and/or bulkhead 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 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("first attempt at providing programmatic API") +public interface FaultTolerance { + /** + * Returns a {@link CircuitBreakerMaintenance} instance that provides maintenance access to existing + * circuit breakers. + */ + static CircuitBreakerMaintenance circuitBreakerMaintenance() { + return FaultToleranceSpiAccess.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. + */ + static Builder> createCallable(Callable action) { + return FaultToleranceSpiAccess.create(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. + */ + static Builder> createSupplier(Supplier action) { + return FaultToleranceSpiAccess.create(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. + */ + static Builder createRunnable(Runnable action) { + return FaultToleranceSpiAccess.create(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()}. + */ + static Builder> create() { + return FaultToleranceSpiAccess.create(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. + */ + static Builder, Callable>> createAsyncCallable( + Callable> action) { + return FaultToleranceSpiAccess.createAsync(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. + */ + static Builder, Supplier>> createAsyncSupplier( + Supplier> action) { + return FaultToleranceSpiAccess.createAsync(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. + */ + static Builder, Runnable> createAsyncRunnable(Runnable action) { + return FaultToleranceSpiAccess.createAsync(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()}. + */ + static Builder, FaultTolerance>> createAsync() { + return FaultToleranceSpiAccess.createAsync(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. + */ + 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. + */ + default T get(Supplier action) { + try { + return call(action::get); + } catch (RuntimeException e) { + throw e; + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + /** + * 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. + */ + default void run(Runnable action) { + try { + call(() -> { + action.run(); + return null; + }); + } catch (RuntimeException e) { + throw e; + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + /** + * 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); + } + + /** + * 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) + */ + default Runnable adaptRunnable(Runnable action) { + return () -> run(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 + * @param type of result of this builder, depends on how this builder was created + */ + interface Builder { + /** + * 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 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 the guarded action should be offloaded to another thread. This is only possible + * for asynchronous actions. If this builder was not created using {@code createAsync}, this method + * throws an exception. + * + * @param value whether the guarded action should be offloaded to another thread + * @return this fault tolerance builder + * @see Asynchronous @Asynchronous + */ + Builder withThreadOffload(boolean value); + + /** + * Returns a ready-to-use instance of {@code FaultTolerance} or guarded {@code Callable}, depending on + * how this builder was created. + */ + 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 + */ + 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 + */ + 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 + */ + BulkheadBuilder queueSize(int value); + + /** + * 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 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 + * that accept a name parameter. Defaults to unnamed. + * + * @param value the circuit breaker name, must not be {@code null} + * @return this circuit breaker builder + * @see CircuitBreakerName @CircuitBreakerName + */ + CircuitBreakerBuilder name(String value); + + /** + * 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))); + } + + /** + * 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 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))); + } + + /** + * 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(); + + /** + * 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); + + /** + * 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/FaultToleranceSpi.java new file mode 100644 index 00000000..e7cb6e13 --- /dev/null +++ b/api/src/main/java/io/smallrye/faulttolerance/api/FaultToleranceSpi.java @@ -0,0 +1,24 @@ +package io.smallrye.faulttolerance.api; + +import java.util.concurrent.CompletionStage; +import java.util.function.Function; + +import io.smallrye.common.annotation.Experimental; + +/** + * This is an internal API. It may change incompatibly without notice. + * It should not be used or implemented outside SmallRye Fault Tolerance. + */ +@Experimental("first attempt at providing programmatic API") +public interface FaultToleranceSpi { + boolean applies(); + + int priority(); + + FaultTolerance.Builder newBuilder(Function, R> finisher); + + FaultTolerance.Builder, R> newAsyncBuilder( + 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/FaultToleranceSpiAccess.java new file mode 100644 index 00000000..0b5de9b8 --- /dev/null +++ b/api/src/main/java/io/smallrye/faulttolerance/api/FaultToleranceSpiAccess.java @@ -0,0 +1,43 @@ +package io.smallrye.faulttolerance.api; + +import java.util.ServiceLoader; +import java.util.concurrent.CompletionStage; +import java.util.function.Function; + +class FaultToleranceSpiAccess { + static class Holder { + static final FaultToleranceSpi INSTANCE = instantiateSpi(); + } + + static FaultTolerance.Builder create(Function, R> finisher) { + return Holder.INSTANCE.newBuilder(finisher); + }; + + static FaultTolerance.Builder, R> createAsync( + Function>, R> finisher) { + return Holder.INSTANCE.newAsyncBuilder(finisher); + } + + static CircuitBreakerMaintenance circuitBreakerMaintenance() { + return Holder.INSTANCE.circuitBreakerMaintenance(); + } + + private static FaultToleranceSpi instantiateSpi() { + FaultToleranceSpi bestCandidate = null; + int bestCandidatePriority = Integer.MIN_VALUE; + for (FaultToleranceSpi candidate : ServiceLoader.load(FaultToleranceSpi.class)) { + if (!candidate.applies()) { + continue; + } + if (candidate.priority() > bestCandidatePriority) { + bestCandidate = candidate; + } + } + + 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"); + } + return bestCandidate; + } +} diff --git a/doc/modules/ROOT/nav.adoc b/doc/modules/ROOT/nav.adoc index b350b2d0..609e6f31 100644 --- a/doc/modules/ROOT/nav.adoc +++ b/doc/modules/ROOT/nav.adoc @@ -2,6 +2,7 @@ * Usage ** xref:usage/basic.adoc[Basic Usage] ** xref:usage/extra.adoc[Extra Features] +** xref:usage/programmatic-api.adoc[Programmatic API] * Integration ** xref:integration/intro.adoc[Introduction] ** xref:integration/thread-pool.adoc[Thread Pool] diff --git a/doc/modules/ROOT/pages/integration/event-loop.adoc b/doc/modules/ROOT/pages/integration/event-loop.adoc index 54df7f3a..4888afb1 100644 --- a/doc/modules/ROOT/pages/integration/event-loop.adoc +++ b/doc/modules/ROOT/pages/integration/event-loop.adoc @@ -10,7 +10,7 @@ The integrator must implement the `EventLoop` interface, which lets {smallrye-fa Implementations are discovered through `ServiceLoader`, so the corresponding `META-INF/services/\...` file must also exist. At most one implementation of `EventLoop` may be present at runtime. -{smallrye-fault-tolerance} will use it to execute `@NonBlocking` guarded methods when the caller was originally on an event loop thread. +{smallrye-fault-tolerance} will use it to execute non-blocking guarded methods when the caller was originally on an event loop thread. This is important in case of: - retries, when there's a delay between retry attempts (the delay must not block); diff --git a/doc/modules/ROOT/pages/integration/intro.adoc b/doc/modules/ROOT/pages/integration/intro.adoc index d94466da..293d6b60 100644 --- a/doc/modules/ROOT/pages/integration/intro.adoc +++ b/doc/modules/ROOT/pages/integration/intro.adoc @@ -2,9 +2,9 @@ {smallrye-fault-tolerance} comprises the following main artifacts: -* `io.smallrye:smallrye-fault-tolerance-api`: public API for the features provided on top of {microprofile-fault-tolerance}; +* `io.smallrye:smallrye-fault-tolerance-api`: public API for the features provided on top of {microprofile-fault-tolerance}, including a programmatic API; * `io.smallrye:smallrye-fault-tolerance-core`: core implementations of fault tolerance strategies; -* `io.smallrye:smallrye-fault-tolerance`: CDI-based implementation of {microprofile-fault-tolerance}; +* `io.smallrye:smallrye-fault-tolerance`: CDI-based implementation of {microprofile-fault-tolerance} with {smallrye-fault-tolerance} extensions, including a programmatic API; * `io.smallrye:smallrye-fault-tolerance-autoconfig-core`: runtime part of the {smallrye-fault-tolerance} configuration system. ** (There's also `io.smallrye:smallrye-fault-tolerance-autoconfig-processor`, but that is only used during {smallrye-fault-tolerance} build and isn't required at runtime.) @@ -28,3 +28,9 @@ The remaining integration concerns are described in the following sections: * xref:integration/context-propagation.adoc[enabling Context Propagation] for `@Asynchronous` methods; * xref:integration/opentracing.adoc[enabling OpenTracing integration] with Context Propagation; * xref:integration/event-loop.adoc[integrating an event loop] for non-blocking method invocations. + +== Standalone Implementation of the Programmatic API + +A completely standalone implementation of the programmatic API exists in the `io.smallrye:smallrye-fault-tolerance-standalone` artifact. +This is supposed to be used in a non-CDI environment. +Runtimes that integrate {smallrye-fault-tolerance} typically do so using CDI, and so should ignore this artifact. \ No newline at end of file diff --git a/doc/modules/ROOT/pages/usage/programmatic-api.adoc b/doc/modules/ROOT/pages/usage/programmatic-api.adoc new file mode 100644 index 00000000..8645e2c4 --- /dev/null +++ b/doc/modules/ROOT/pages/usage/programmatic-api.adoc @@ -0,0 +1,282 @@ += Programmatic API + +In addition to the declarative, annotation-based API of {microprofile-fault-tolerance}, {smallrye-fault-tolerance} also offers a programmatic API for advanced scenarios. +This API is present in the `io.smallrye:smallrye-fault-tolerance-api` artifact, just like all other additional APIs {smallrye-fault-tolerance} provides. + +== Installation + +If you use {smallrye-fault-tolerance} as part of a runtime that implements {microprofile-fault-tolerance}, you don't have to do anything. +The programmatic API is ready to use. +In this documentation, we'll call this the _CDI implementation_ of the programmatic API, because it's integrated with the {microprofile-fault-tolerance} implementation, which is naturally based on CDI. + +.Quarkus +**** +In Quarkus, the {smallrye-fault-tolerance} API is brought in automatically, as a transitive dependency of the Quarkus extension for {smallrye-fault-tolerance}. +That is, you don't need to do anything to be able to use the programmatic API. +**** + +.WildFly +**** +In WildFly, the {smallrye-fault-tolerance} API is not readily available to deployments. +If you want to use it, you need to add a module dependency to the deployment using `jboss-deployment-structure.xml`. + +Note that at the time of this writing, the {smallrye-fault-tolerance} module in WildFly is considered private. +If you do add a module dependency on it, to be able to use the {smallrye-fault-tolerance} API, you may be stepping out of the WildFly support scope. +**** + +In addition to the CDI implementation, {smallrye-fault-tolerance} also offers a _standalone implementation_ that is meant to be used outside of any runtime. +This implementation does not need CDI or anything else. +If you want to use {smallrye-fault-tolerance} in a standalone fashion, just add a dependency on `io.smallrye:smallrye-fault-tolerance-standalone`. +The API is brought in transitively. + +== Usage + +The entrypoint to the programmatic API is the `FaultTolerance` interface. + +This interface represents 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. +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. + +.Disabling Fault Tolerance +**** +There's one exception to the "no external configuration" rule. + +The CDI implementation looks for the well-known `MP_Fault_Tolerance_NonFallback_Enabled` configuration property in MicroProfile Config. +The standalone implementation looks for a system property with the same name. + +If such property exists and is set to `false`, only the fallback and thread offload fault tolerance strategies will be applied. +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. +It is _not_ read again later. +**** + +Let's take a look at a simple example: + +[source,java] +---- +public class MyService { + public String hello() throws Exception { + FaultTolerance guarded = FaultTolerance.create() // <1> + .withFallback().handler(() -> "fallback").done() // <2> + .build(); // <3> + + return guarded.call(() -> externalService.hello()); // <4> + } +} +---- + +<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`). + +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. +Unlike the declarative API, the programmatic API doesn't support asynchronous actions that return the `Future` type. + +[source,java] +---- +public class MyService { + public CompletionStage hello() throws Exception { + FaultTolerance> guarded = FaultTolerance.createAsync() // <1> + .withBulkhead().done() // <2> + .withThreadOffload(true) // <3> + .build(); + + return guarded.call(() -> externalService.hello()); // <4> + } +} +---- + +<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`. + +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. +This is often desired for non-blocking actions, which are very common in modern reactive architectures. + +=== 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. + +First off, a `FaultTolerance` instance may be adapted to a `Callable`, `Supplier` or `Runnable` using the `adapt*` methods. +For example: + +[source,java] +---- +public class MyService { + private static final FaultTolerance guard = FaultTolerance.create() + .withTimeout().duration(5, ChronoUnit.SECONDS).done() + .build(); // <1> + + 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`). +<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: + +[source, java] +---- +public class MyService { + private final Callable guard = FaultTolerance.createCallable(externalService::hello) // <1> + .withTimeout().duration(5, ChronoUnit.SECONDS).done() + .build(); + + public String hello() throws Exception { + guard.call(); // <2> + } +} +---- + +<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. + +=== Stateful Fault Tolerance Strategies + +The circuit breaker and bulkhead strategies are stateful. +That is, they hold some state required for their correct functioning, such as the number of current executions for bulkhead, or the rolling window of successes/failures for circuit breaker. +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 and/or circuit breaker. +If, on the other hand, you use different `FaultTolerance` objects for guarding different actions, each action will be guarded by its own bulkhead and/or circuit breaker. + +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. + +=== Summary of `FaultTolerance` Methods + +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. + +|=== +| The builder result type | Synchronous actions | Asynchronous actions + +| `FaultTolerance` +| `create()` -> `FaultTolerance` +| `createAsync()` -> `FaultTolerance>` + +| `Callable` +| `createCallable(Callable)` -> `Callable` +| `createAsyncCallable(Callable>)` -> `Callable>` + +| `Supplier` +| `createSupplier(Supplier)` -> `Supplier` +| `createAsyncSupplier(Supplier>)` -> `Supplier>` + +| `Runnable` +| `createRunnable(Runnable)` -> `Runnable` +| `createAsyncRunnable(Runnable)` -> `Runnable` +|=== + +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. + +|=== +| The action type | Execute | Adapt + +| `Callable` +| `call(Callable)` -> `T` +| `adaptCallable(Callable)` -> `Callable` + +| `Supplier` +| `get(Supplier)` -> `T` +| `adaptSupplier(Supplier)` -> `Supplier` + +| `Runnable` +| `run(Runnable)` -> `void` +| `adaptRunnable(Runnable)` -> `Runnable` +|=== + +== Configuration and Metrics + +As mentioned above, with the single exception of `MP_Fault_Tolerance_NonFallback_Enabled`, there is no external configuration support. +This may change in the future, though possibly only in the CDI implementation. + +At the moment, the programmatic API of {smallrye-fault-tolerance} is not integrated with metrics. +This will change in the future, though possibly only in the CDI implementation. + +== Integration Concerns + +=== Standalone Implementation + +The standalone implementation provides no integration points. +It is, as the name suggests, completely standalone. + +At the moment, it is not possible to change the thread pool to which actions are offloaded. +A single cached thread pool, obtained using `Executors.newCachedThreadPool()`, is used for all thread offloads. + +Users of the standalone implementation that also use an event loop based library, such as Vert.x, may integrate the event loop support as described in xref:integration/event-loop.adoc[Event Loop]. + +=== CDI implementation + +The CDI implementation will use the thread pool an integrator provides (see xref:integration/thread-pool.adoc[Thread Pool]). +This also extends to the context propagation integration (see xref:integration/context-propagation.adoc[Context Propagation]). + +It will also use the event loop support, if integrator provides one (see xref:integration/event-loop.adoc[Event Loop]). diff --git a/implementation/core/pom.xml b/implementation/core/pom.xml index 48a067d4..aa8c0e77 100644 --- a/implementation/core/pom.xml +++ b/implementation/core/pom.xml @@ -90,9 +90,11 @@ - io/smallrye/faulttolerance/core/util/barrier/* - io/smallrye/faulttolerance/core/util/party/* + io/smallrye/faulttolerance/core/util/** + + **/*Test.class + @@ -107,9 +109,11 @@ - io/smallrye/faulttolerance/core/util/barrier/* - io/smallrye/faulttolerance/core/util/party/* + io/smallrye/faulttolerance/core/util/** + + **/*Test.java + diff --git a/implementation/core/src/main/java/io/smallrye/faulttolerance/core/util/SetOfThrowables.java b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/util/SetOfThrowables.java index 4819aef1..d10be9f1 100644 --- a/implementation/core/src/main/java/io/smallrye/faulttolerance/core/util/SetOfThrowables.java +++ b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/util/SetOfThrowables.java @@ -1,8 +1,8 @@ package io.smallrye.faulttolerance.core.util; +import java.util.Collection; import java.util.Collections; import java.util.HashSet; -import java.util.List; import java.util.Set; public class SetOfThrowables { @@ -25,7 +25,7 @@ public static SetOfThrowables create(Class clazz) { * @param classes throwable classes to include in the set * @return a set of throwable classes */ - public static SetOfThrowables create(List> classes) { + public static SetOfThrowables create(Collection> classes) { Set> set = new HashSet<>(classes); return new SetOfThrowables(set); } diff --git a/implementation/core/src/test/java/io/smallrye/faulttolerance/core/util/Timing.java b/implementation/core/src/test/java/io/smallrye/faulttolerance/core/util/Timing.java new file mode 100644 index 00000000..ea487834 --- /dev/null +++ b/implementation/core/src/test/java/io/smallrye/faulttolerance/core/util/Timing.java @@ -0,0 +1,14 @@ +package io.smallrye.faulttolerance.core.util; + +public class Timing { + public interface Action { + void run() throws Exception; + } + + public static long timed(Action action) throws Exception { + long start = System.nanoTime(); + action.run(); + long end = System.nanoTime(); + return (end - start) / 1_000_000; + } +} diff --git a/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/CdiFaultTolerance.java b/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/CdiFaultTolerance.java new file mode 100644 index 00000000..37c48ea9 --- /dev/null +++ b/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/CdiFaultTolerance.java @@ -0,0 +1,626 @@ +package io.smallrye.faulttolerance; + +import static io.smallrye.faulttolerance.core.Invocation.invocation; + +import java.time.Duration; +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.CompletionStage; +import java.util.concurrent.Executor; +import java.util.function.Function; +import java.util.function.Supplier; + +import io.smallrye.faulttolerance.api.CustomBackoffStrategy; +import io.smallrye.faulttolerance.api.FaultTolerance; +import io.smallrye.faulttolerance.core.FaultToleranceStrategy; +import io.smallrye.faulttolerance.core.InvocationContext; +import io.smallrye.faulttolerance.core.async.CompletionStageExecution; +import io.smallrye.faulttolerance.core.async.RememberEventLoop; +import io.smallrye.faulttolerance.core.bulkhead.CompletionStageThreadPoolBulkhead; +import io.smallrye.faulttolerance.core.bulkhead.SemaphoreBulkhead; +import io.smallrye.faulttolerance.core.circuit.breaker.CircuitBreaker; +import io.smallrye.faulttolerance.core.circuit.breaker.CompletionStageCircuitBreaker; +import io.smallrye.faulttolerance.core.event.loop.EventLoop; +import io.smallrye.faulttolerance.core.fallback.CompletionStageFallback; +import io.smallrye.faulttolerance.core.fallback.Fallback; +import io.smallrye.faulttolerance.core.fallback.FallbackFunction; +import io.smallrye.faulttolerance.core.retry.BackOff; +import io.smallrye.faulttolerance.core.retry.CompletionStageRetry; +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.CompletionStageTimeout; +import io.smallrye.faulttolerance.core.timeout.Timeout; +import io.smallrye.faulttolerance.core.timeout.TimerTimeoutWatcher; +import io.smallrye.faulttolerance.core.timer.Timer; +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.SetOfThrowables; + +// this is mostly a copy of StandaloneFaultTolerance and they must be kept in sync +class CdiFaultTolerance implements FaultTolerance { + private final FaultToleranceStrategy strategy; + + CdiFaultTolerance(FaultToleranceStrategy strategy) { + this.strategy = strategy; + } + + @Override + public T call(Callable action) throws Exception { + InvocationContext ctx = new InvocationContext<>(action); + return strategy.apply(ctx); + } + + static class BuilderImpl implements Builder { + private final boolean ftEnabled; + private final Executor executor; + private final Timer timer; + private final EventLoop eventLoop; + private final CircuitBreakerMaintenanceImpl cbMaintenance; + private final boolean isAsync; + private final Function, R> finisher; + + private BulkheadBuilderImpl bulkheadBuilder; + private CircuitBreakerBuilderImpl circuitBreakerBuilder; + private FallbackBuilderImpl fallbackBuilder; + private RetryBuilderImpl retryBuilder; + private TimeoutBuilderImpl timeoutBuilder; + private boolean offloadToAnotherThread; + + BuilderImpl(boolean ftEnabled, Executor executor, Timer timer, EventLoop eventLoop, + CircuitBreakerMaintenanceImpl cbMaintenance, boolean isAsync, + Function, R> finisher) { + this.ftEnabled = ftEnabled; + this.executor = executor; + this.timer = timer; + this.eventLoop = eventLoop; + this.cbMaintenance = cbMaintenance; + this.isAsync = isAsync; + this.finisher = finisher; + } + + @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 RetryBuilder withRetry() { + return new RetryBuilderImpl<>(this); + } + + @Override + public TimeoutBuilder withTimeout() { + return new TimeoutBuilderImpl<>(this); + } + + @Override + public Builder withThreadOffload(boolean value) { + if (!isAsync) { + throw new IllegalStateException("Thread offload may only be set for asynchronous invocations"); + } + + this.offloadToAnotherThread = value; + return this; + } + + @Override + public R build() { + FaultTolerance result; + if (isAsync) { + result = new CdiFaultTolerance(buildAsyncStrategy()); + } else { + result = new CdiFaultTolerance<>(buildSyncStrategy()); + } + return finisher.apply(result); + } + + private FaultToleranceStrategy buildSyncStrategy() { + FaultToleranceStrategy result = invocation(); + + if (ftEnabled && bulkheadBuilder != null) { + result = new SemaphoreBulkhead<>(result, "unknown", bulkheadBuilder.limit); + } + + if (ftEnabled && timeoutBuilder != null) { + result = new Timeout<>(result, "unknown", timeoutBuilder.durationInMillis, + new TimerTimeoutWatcher(timer)); + } + + if (ftEnabled && circuitBreakerBuilder != null) { + result = new CircuitBreaker<>(result, "unknown", + createExceptionDecision(circuitBreakerBuilder.skipOn, circuitBreakerBuilder.failOn), + circuitBreakerBuilder.delayInMillis, + circuitBreakerBuilder.requestVolumeThreshold, + circuitBreakerBuilder.failureRatio, + circuitBreakerBuilder.successThreshold, + new SystemStopwatch()); + + String cbName = circuitBreakerBuilder.name != null ? circuitBreakerBuilder.name : UUID.randomUUID().toString(); + cbMaintenance.register(cbName, (CircuitBreaker) result); + } + + if (ftEnabled && retryBuilder != null) { + Supplier backoff = prepareRetryBackoff(retryBuilder); + + result = new Retry<>(result, "unknown", + createExceptionDecision(retryBuilder.abortOn, retryBuilder.retryOn), retryBuilder.maxRetries, + retryBuilder.maxDurationInMillis, () -> new ThreadSleepDelay(backoff.get()), + new SystemStopwatch()); + } + + // fallback is always enabled + if (fallbackBuilder != null) { + FallbackFunction fallbackFunction = ctx -> fallbackBuilder.handler.apply(ctx.failure); + result = new Fallback<>(result, "unknown", fallbackFunction, + createExceptionDecision(fallbackBuilder.skipOn, fallbackBuilder.applyOn)); + } + + return result; + } + + private FaultToleranceStrategy> buildAsyncStrategy() { + FaultToleranceStrategy> result = invocation(); + + // thread offload is always enabled + Executor executor = offloadToAnotherThread ? this.executor : DirectExecutor.INSTANCE; + result = new CompletionStageExecution<>(result, executor); + + if (ftEnabled && bulkheadBuilder != null) { + result = new CompletionStageThreadPoolBulkhead<>(result, "unknown", bulkheadBuilder.limit, + bulkheadBuilder.queueSize); + } + + if (ftEnabled && timeoutBuilder != null) { + result = new CompletionStageTimeout<>(result, "unknown", timeoutBuilder.durationInMillis, + new TimerTimeoutWatcher(timer)); + } + + if (ftEnabled && circuitBreakerBuilder != null) { + result = new CompletionStageCircuitBreaker<>(result, "unknown", + createExceptionDecision(circuitBreakerBuilder.skipOn, circuitBreakerBuilder.failOn), + circuitBreakerBuilder.delayInMillis, + circuitBreakerBuilder.requestVolumeThreshold, + circuitBreakerBuilder.failureRatio, + circuitBreakerBuilder.successThreshold, + new SystemStopwatch()); + + String cbName = circuitBreakerBuilder.name != null ? circuitBreakerBuilder.name : UUID.randomUUID().toString(); + cbMaintenance.register(cbName, (CircuitBreaker) result); + } + + if (ftEnabled && retryBuilder != null) { + Supplier backoff = prepareRetryBackoff(retryBuilder); + + result = new CompletionStageRetry<>(result, "unknown", + createExceptionDecision(retryBuilder.abortOn, retryBuilder.retryOn), retryBuilder.maxRetries, + retryBuilder.maxDurationInMillis, () -> new TimerDelay(backoff.get(), timer), + new SystemStopwatch()); + } + + // fallback is always enabled + if (fallbackBuilder != null) { + FallbackFunction> fallbackFunction = ctx -> (CompletionStage) fallbackBuilder.handler + .apply(ctx.failure); + result = new CompletionStageFallback<>(result, "unknown", fallbackFunction, + createExceptionDecision(fallbackBuilder.skipOn, fallbackBuilder.applyOn)); + } + + // thread offload is always enabled + if (!offloadToAnotherThread) { + result = new RememberEventLoop<>(result, eventLoop); + } + + return result; + } + + private static long getTimeInMs(long time, ChronoUnit unit) { + return Duration.of(time, unit).toMillis(); + } + + private static ExceptionDecision createExceptionDecision(Collection> consideredExpected, + Collection> consideredFailure) { + return new ExceptionDecision(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; + + 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) { + if (!parent.isAsync) { + throw new IllegalStateException("Bulkhead queue size may only be set for asynchronous invocations"); + } + + this.queueSize = Preconditions.check(value, value >= 1, "Queue size must be >= 1"); + 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 long delayInMillis = 5000; + private int requestVolumeThreshold = 20; + private double failureRatio = 0.5; + private int successThreshold = 1; + + private String name; // unnamed by default + + CircuitBreakerBuilderImpl(BuilderImpl parent) { + this.parent = parent; + } + + @Override + public CircuitBreakerBuilder failOn(Collection> value) { + this.failOn = Preconditions.checkNotNull(value, "Exceptions considered failure must be set"); + return this; + } + + @Override + public CircuitBreakerBuilder skipOn(Collection> value) { + this.skipOn = Preconditions.checkNotNull(value, "Exceptions considered success 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 = getTimeInMs(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 Builder done() { + 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(); + + 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"); + return this; + } + + @Override + public FallbackBuilder skipOn(Collection> value) { + this.skipOn = Preconditions.checkNotNull(value, "Exceptions to skip fallback on must be set"); + return this; + } + + @Override + public Builder done() { + Preconditions.checkNotNull(handler, "Fallback handler must be set"); + + parent.fallbackBuilder = 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 ExponentialBackoffBuilderImpl exponentialBackoffBuilder; + private FibonacciBackoffBuilderImpl fibonacciBackoffBuilder; + private CustomBackoffBuilderImpl customBackoffBuilder; + + 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 = getTimeInMs(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 = getTimeInMs(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 = getTimeInMs(value, unit); + return this; + } + + @Override + public RetryBuilder retryOn(Collection> value) { + this.retryOn = Preconditions.checkNotNull(value, "Exceptions to retry on must be set"); + return this; + } + + @Override + public RetryBuilder abortOn(Collection> value) { + this.abortOn = Preconditions.checkNotNull(value, "Exceptions to abort retrying on 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 Builder done() { + 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 = getTimeInMs(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 = getTimeInMs(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; + + 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 = getTimeInMs(value, unit); + return this; + } + + @Override + public Builder done() { + parent.timeoutBuilder = this; + return parent; + } + } + } +} diff --git a/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/CdiFaultToleranceSpi.java b/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/CdiFaultToleranceSpi.java new file mode 100644 index 00000000..f44f98a3 --- /dev/null +++ b/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/CdiFaultToleranceSpi.java @@ -0,0 +1,84 @@ +package io.smallrye.faulttolerance; + +import java.util.concurrent.CompletionStage; +import java.util.concurrent.ExecutorService; +import java.util.function.Function; + +import javax.enterprise.inject.spi.CDI; +import javax.inject.Inject; +import javax.inject.Singleton; + +import org.eclipse.microprofile.config.inject.ConfigProperty; + +import io.smallrye.faulttolerance.api.CircuitBreakerMaintenance; +import io.smallrye.faulttolerance.api.FaultTolerance; +import io.smallrye.faulttolerance.api.FaultToleranceSpi; +import io.smallrye.faulttolerance.core.event.loop.EventLoop; +import io.smallrye.faulttolerance.core.timer.Timer; + +public class CdiFaultToleranceSpi implements FaultToleranceSpi { + @Singleton + static class Dependencies { + @Inject + @ConfigProperty(name = "MP_Fault_Tolerance_NonFallback_Enabled", defaultValue = "true") + boolean ftEnabled; + + @Inject + ExecutorHolder executorHolder; + + @Inject + CircuitBreakerMaintenanceImpl cbMaintenance; + + ExecutorService asyncExecutor() { + return executorHolder.getAsyncExecutor(); + } + + EventLoop eventLoop() { + return executorHolder.getEventLoop(); + } + + Timer timer() { + return executorHolder.getTimer(); + } + } + + private Dependencies getDependencies() { + // always lookup from current CDI container, because there's no guarantee that `CDI.current()` will always be + // the same (in certain environments, e.g. in the test suite, CDI containers come and go freely) + return CDI.current().select(Dependencies.class).get(); + } + + @Override + public boolean applies() { + try { + return getDependencies() != null; + } catch (Exception ignored) { + return false; + } + } + + @Override + public int priority() { + return 1000; + } + + @Override + public FaultTolerance.Builder newBuilder(Function, R> finisher) { + Dependencies deps = getDependencies(); + return new CdiFaultTolerance.BuilderImpl<>(deps.ftEnabled, deps.asyncExecutor(), deps.timer(), deps.eventLoop(), + deps.cbMaintenance, false, finisher); + } + + @Override + public FaultTolerance.Builder, R> newAsyncBuilder( + Function>, R> finisher) { + Dependencies deps = getDependencies(); + return new CdiFaultTolerance.BuilderImpl<>(deps.ftEnabled, deps.asyncExecutor(), deps.timer(), deps.eventLoop(), + deps.cbMaintenance, true, finisher); + } + + @Override + public CircuitBreakerMaintenance circuitBreakerMaintenance() { + return getDependencies().cbMaintenance; + } +} 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 e00bf94f..204e1d4b 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 @@ -116,6 +116,8 @@ 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.Dependencies.class), + CdiFaultToleranceSpi.Dependencies.class.getName()); } void changeInterceptorPriority(@Observes ProcessAnnotatedType event) { 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 new file mode 100644 index 00000000..fd1670d9 --- /dev/null +++ b/implementation/fault-tolerance/src/main/resources/META-INF/services/io.smallrye.faulttolerance.api.FaultToleranceSpi @@ -0,0 +1 @@ +io.smallrye.faulttolerance.CdiFaultToleranceSpi \ No newline at end of file diff --git a/implementation/pom.xml b/implementation/pom.xml index 939730b2..ed8bc142 100644 --- a/implementation/pom.xml +++ b/implementation/pom.xml @@ -35,5 +35,7 @@ context-propagation tracing-propagation vertx + + standalone diff --git a/implementation/standalone/pom.xml b/implementation/standalone/pom.xml new file mode 100644 index 00000000..03840dbc --- /dev/null +++ b/implementation/standalone/pom.xml @@ -0,0 +1,94 @@ + + + 4.0.0 + + + io.smallrye + smallrye-fault-tolerance-implementation-parent + 5.2.2-SNAPSHOT + + + smallrye-fault-tolerance-standalone + + SmallRye Fault Tolerance: Standalone Implementation + + + + io.smallrye + smallrye-fault-tolerance-api + + + jakarta.enterprise + jakarta.enterprise.cdi-api + + + org.eclipse.microprofile.fault-tolerance + microprofile-fault-tolerance-api + + + + + io.smallrye + smallrye-fault-tolerance-core + + + + io.smallrye + smallrye-fault-tolerance-core + test-jar + test + + + + org.junit.jupiter + junit-jupiter + test + + + org.junit-pioneer + junit-pioneer + test + + + org.assertj + assertj-core + test + + + + + + + org.apache.maven.plugins + maven-jar-plugin + + + + test-jar + + + + + + org.apache.maven.plugins + maven-source-plugin + + + + test-jar-no-fork + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + + ${project.build.testOutputDirectory}/logging.properties + + + + + + diff --git a/implementation/standalone/src/main/java/io/smallrye/faulttolerance/standalone/StandaloneCircuitBreakerMaintenance.java b/implementation/standalone/src/main/java/io/smallrye/faulttolerance/standalone/StandaloneCircuitBreakerMaintenance.java new file mode 100644 index 00000000..01cd3b6c --- /dev/null +++ b/implementation/standalone/src/main/java/io/smallrye/faulttolerance/standalone/StandaloneCircuitBreakerMaintenance.java @@ -0,0 +1,52 @@ +package io.smallrye.faulttolerance.standalone; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +import io.smallrye.faulttolerance.api.CircuitBreakerMaintenance; +import io.smallrye.faulttolerance.api.CircuitBreakerState; +import io.smallrye.faulttolerance.core.circuit.breaker.CircuitBreaker; + +class StandaloneCircuitBreakerMaintenance implements CircuitBreakerMaintenance { + private final ConcurrentMap> registry = new ConcurrentHashMap<>(); + + void register(String circuitBreakerName, CircuitBreaker circuitBreaker) { + registry.putIfAbsent(circuitBreakerName, circuitBreaker); + } + + @Override + public CircuitBreakerState currentState(String name) { + CircuitBreaker circuitBreaker = registry.get(name); + if (circuitBreaker == null) { + // if the circuit breaker wasn't instantiated yet, it's "closed" by definition + return CircuitBreakerState.CLOSED; + } + + int currentState = circuitBreaker.currentState(); + switch (currentState) { + case CircuitBreaker.STATE_CLOSED: + return CircuitBreakerState.CLOSED; + case CircuitBreaker.STATE_OPEN: + return CircuitBreakerState.OPEN; + case CircuitBreaker.STATE_HALF_OPEN: + return CircuitBreakerState.HALF_OPEN; + default: + throw new IllegalStateException("Unknown circuit breaker state " + currentState); + } + } + + @Override + public void reset(String name) { + CircuitBreaker circuitBreaker = registry.get(name); + if (circuitBreaker != null) { + // if the circuit breaker wasn't instantiated yet, "resetting it" is by definition a noop + circuitBreaker.reset(); + } + } + + @Override + public void resetAll() { + // circuit breakers that weren't instantiated yet don't have to be reset + registry.values().forEach(CircuitBreaker::reset); + } +} diff --git a/implementation/standalone/src/main/java/io/smallrye/faulttolerance/standalone/StandaloneFaultTolerance.java b/implementation/standalone/src/main/java/io/smallrye/faulttolerance/standalone/StandaloneFaultTolerance.java new file mode 100644 index 00000000..8f935fe2 --- /dev/null +++ b/implementation/standalone/src/main/java/io/smallrye/faulttolerance/standalone/StandaloneFaultTolerance.java @@ -0,0 +1,626 @@ +package io.smallrye.faulttolerance.standalone; + +import static io.smallrye.faulttolerance.core.Invocation.invocation; + +import java.time.Duration; +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.CompletionStage; +import java.util.concurrent.Executor; +import java.util.function.Function; +import java.util.function.Supplier; + +import io.smallrye.faulttolerance.api.CustomBackoffStrategy; +import io.smallrye.faulttolerance.api.FaultTolerance; +import io.smallrye.faulttolerance.core.FaultToleranceStrategy; +import io.smallrye.faulttolerance.core.InvocationContext; +import io.smallrye.faulttolerance.core.async.CompletionStageExecution; +import io.smallrye.faulttolerance.core.async.RememberEventLoop; +import io.smallrye.faulttolerance.core.bulkhead.CompletionStageThreadPoolBulkhead; +import io.smallrye.faulttolerance.core.bulkhead.SemaphoreBulkhead; +import io.smallrye.faulttolerance.core.circuit.breaker.CircuitBreaker; +import io.smallrye.faulttolerance.core.circuit.breaker.CompletionStageCircuitBreaker; +import io.smallrye.faulttolerance.core.event.loop.EventLoop; +import io.smallrye.faulttolerance.core.fallback.CompletionStageFallback; +import io.smallrye.faulttolerance.core.fallback.Fallback; +import io.smallrye.faulttolerance.core.fallback.FallbackFunction; +import io.smallrye.faulttolerance.core.retry.BackOff; +import io.smallrye.faulttolerance.core.retry.CompletionStageRetry; +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.CompletionStageTimeout; +import io.smallrye.faulttolerance.core.timeout.Timeout; +import io.smallrye.faulttolerance.core.timeout.TimerTimeoutWatcher; +import io.smallrye.faulttolerance.core.timer.Timer; +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.SetOfThrowables; + +// this is mostly a copy of CdiFaultTolerance and they must be kept in sync +class StandaloneFaultTolerance implements FaultTolerance { + private final FaultToleranceStrategy strategy; + + StandaloneFaultTolerance(FaultToleranceStrategy strategy) { + this.strategy = strategy; + } + + @Override + public T call(Callable action) throws Exception { + InvocationContext ctx = new InvocationContext<>(action); + return strategy.apply(ctx); + } + + static class BuilderImpl implements Builder { + private final boolean ftEnabled; + private final Executor executor; + private final Timer timer; + private final EventLoop eventLoop; + private final StandaloneCircuitBreakerMaintenance cbMaintenance; + private final boolean isAsync; + private final Function, R> finisher; + + private BulkheadBuilderImpl bulkheadBuilder; + private CircuitBreakerBuilderImpl circuitBreakerBuilder; + private FallbackBuilderImpl fallbackBuilder; + private RetryBuilderImpl retryBuilder; + private TimeoutBuilderImpl timeoutBuilder; + private boolean offloadToAnotherThread; + + BuilderImpl(boolean ftEnabled, Executor executor, Timer timer, EventLoop eventLoop, + StandaloneCircuitBreakerMaintenance cbMaintenance, boolean isAsync, + Function, R> finisher) { + this.ftEnabled = ftEnabled; + this.executor = executor; + this.timer = timer; + this.eventLoop = eventLoop; + this.cbMaintenance = cbMaintenance; + this.isAsync = isAsync; + this.finisher = finisher; + } + + @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 RetryBuilder withRetry() { + return new RetryBuilderImpl<>(this); + } + + @Override + public TimeoutBuilder withTimeout() { + return new TimeoutBuilderImpl<>(this); + } + + @Override + public Builder withThreadOffload(boolean value) { + if (!isAsync) { + throw new IllegalStateException("Thread offload may only be set for asynchronous invocations"); + } + + this.offloadToAnotherThread = value; + return this; + } + + @Override + public R build() { + FaultTolerance result; + if (isAsync) { + result = new StandaloneFaultTolerance(buildAsyncStrategy()); + } else { + result = new StandaloneFaultTolerance<>(buildSyncStrategy()); + } + return finisher.apply(result); + } + + private FaultToleranceStrategy buildSyncStrategy() { + FaultToleranceStrategy result = invocation(); + + if (ftEnabled && bulkheadBuilder != null) { + result = new SemaphoreBulkhead<>(result, "unknown", bulkheadBuilder.limit); + } + + if (ftEnabled && timeoutBuilder != null) { + result = new Timeout<>(result, "unknown", timeoutBuilder.durationInMillis, + new TimerTimeoutWatcher(timer)); + } + + if (ftEnabled && circuitBreakerBuilder != null) { + result = new CircuitBreaker<>(result, "unknown", + createExceptionDecision(circuitBreakerBuilder.skipOn, circuitBreakerBuilder.failOn), + circuitBreakerBuilder.delayInMillis, + circuitBreakerBuilder.requestVolumeThreshold, + circuitBreakerBuilder.failureRatio, + circuitBreakerBuilder.successThreshold, + new SystemStopwatch()); + + String cbName = circuitBreakerBuilder.name != null ? circuitBreakerBuilder.name : UUID.randomUUID().toString(); + cbMaintenance.register(cbName, (CircuitBreaker) result); + } + + if (ftEnabled && retryBuilder != null) { + Supplier backoff = prepareRetryBackoff(retryBuilder); + + result = new Retry<>(result, "unknown", + createExceptionDecision(retryBuilder.abortOn, retryBuilder.retryOn), retryBuilder.maxRetries, + retryBuilder.maxDurationInMillis, () -> new ThreadSleepDelay(backoff.get()), + new SystemStopwatch()); + } + + // fallback is always enabled + if (fallbackBuilder != null) { + FallbackFunction fallbackFunction = ctx -> fallbackBuilder.handler.apply(ctx.failure); + result = new Fallback<>(result, "unknown", fallbackFunction, + createExceptionDecision(fallbackBuilder.skipOn, fallbackBuilder.applyOn)); + } + + return result; + } + + private FaultToleranceStrategy> buildAsyncStrategy() { + FaultToleranceStrategy> result = invocation(); + + // thread offload is always enabled + Executor executor = offloadToAnotherThread ? this.executor : DirectExecutor.INSTANCE; + result = new CompletionStageExecution<>(result, executor); + + if (ftEnabled && bulkheadBuilder != null) { + result = new CompletionStageThreadPoolBulkhead<>(result, "unknown", bulkheadBuilder.limit, + bulkheadBuilder.queueSize); + } + + if (ftEnabled && timeoutBuilder != null) { + result = new CompletionStageTimeout<>(result, "unknown", timeoutBuilder.durationInMillis, + new TimerTimeoutWatcher(timer)); + } + + if (ftEnabled && circuitBreakerBuilder != null) { + result = new CompletionStageCircuitBreaker<>(result, "unknown", + createExceptionDecision(circuitBreakerBuilder.skipOn, circuitBreakerBuilder.failOn), + circuitBreakerBuilder.delayInMillis, + circuitBreakerBuilder.requestVolumeThreshold, + circuitBreakerBuilder.failureRatio, + circuitBreakerBuilder.successThreshold, + new SystemStopwatch()); + + String cbName = circuitBreakerBuilder.name != null ? circuitBreakerBuilder.name : UUID.randomUUID().toString(); + cbMaintenance.register(cbName, (CircuitBreaker) result); + } + + if (ftEnabled && retryBuilder != null) { + Supplier backoff = prepareRetryBackoff(retryBuilder); + + result = new CompletionStageRetry<>(result, "unknown", + createExceptionDecision(retryBuilder.abortOn, retryBuilder.retryOn), retryBuilder.maxRetries, + retryBuilder.maxDurationInMillis, () -> new TimerDelay(backoff.get(), timer), + new SystemStopwatch()); + } + + // fallback is always enabled + if (fallbackBuilder != null) { + FallbackFunction> fallbackFunction = ctx -> (CompletionStage) fallbackBuilder.handler + .apply(ctx.failure); + result = new CompletionStageFallback<>(result, "unknown", fallbackFunction, + createExceptionDecision(fallbackBuilder.skipOn, fallbackBuilder.applyOn)); + } + + // thread offload is always enabled + if (!offloadToAnotherThread) { + result = new RememberEventLoop<>(result, eventLoop); + } + + return result; + } + + private static long getTimeInMs(long time, ChronoUnit unit) { + return Duration.of(time, unit).toMillis(); + } + + private static ExceptionDecision createExceptionDecision(Collection> consideredExpected, + Collection> consideredFailure) { + return new ExceptionDecision(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; + + 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) { + if (!parent.isAsync) { + throw new IllegalStateException("Bulkhead queue size may only be set for asynchronous invocations"); + } + + this.queueSize = Preconditions.check(value, value >= 1, "Queue size must be >= 1"); + 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 long delayInMillis = 5000; + private int requestVolumeThreshold = 20; + private double failureRatio = 0.5; + private int successThreshold = 1; + + private String name; // unnamed by default + + CircuitBreakerBuilderImpl(BuilderImpl parent) { + this.parent = parent; + } + + @Override + public CircuitBreakerBuilder failOn(Collection> value) { + this.failOn = Preconditions.checkNotNull(value, "Exceptions considered failure must be set"); + return this; + } + + @Override + public CircuitBreakerBuilder skipOn(Collection> value) { + this.skipOn = Preconditions.checkNotNull(value, "Exceptions considered success 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 = getTimeInMs(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 Builder done() { + 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(); + + 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"); + return this; + } + + @Override + public FallbackBuilder skipOn(Collection> value) { + this.skipOn = Preconditions.checkNotNull(value, "Exceptions to skip fallback on must be set"); + return this; + } + + @Override + public Builder done() { + Preconditions.checkNotNull(handler, "Fallback handler must be set"); + + parent.fallbackBuilder = 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 ExponentialBackoffBuilderImpl exponentialBackoffBuilder; + private FibonacciBackoffBuilderImpl fibonacciBackoffBuilder; + private CustomBackoffBuilderImpl customBackoffBuilder; + + 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 = getTimeInMs(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 = getTimeInMs(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 = getTimeInMs(value, unit); + return this; + } + + @Override + public RetryBuilder retryOn(Collection> value) { + this.retryOn = Preconditions.checkNotNull(value, "Exceptions to retry on must be set"); + return this; + } + + @Override + public RetryBuilder abortOn(Collection> value) { + this.abortOn = Preconditions.checkNotNull(value, "Exceptions to abort retrying on 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 Builder done() { + 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 = getTimeInMs(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 = getTimeInMs(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; + + 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 = getTimeInMs(value, unit); + return this; + } + + @Override + public Builder done() { + parent.timeoutBuilder = this; + return parent; + } + } + } +} diff --git a/implementation/standalone/src/main/java/io/smallrye/faulttolerance/standalone/StandaloneFaultToleranceSpi.java b/implementation/standalone/src/main/java/io/smallrye/faulttolerance/standalone/StandaloneFaultToleranceSpi.java new file mode 100644 index 00000000..7fb74842 --- /dev/null +++ b/implementation/standalone/src/main/java/io/smallrye/faulttolerance/standalone/StandaloneFaultToleranceSpi.java @@ -0,0 +1,59 @@ +package io.smallrye.faulttolerance.standalone; + +import java.util.concurrent.CompletionStage; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; +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.core.event.loop.EventLoop; +import io.smallrye.faulttolerance.core.timer.Timer; + +public class StandaloneFaultToleranceSpi implements FaultToleranceSpi { + static class Dependencies { + boolean ftEnabled = !"false".equals(System.getProperty("MP_Fault_Tolerance_NonFallback_Enabled")); + + // TODO let users integrate their own thread pool + final Executor executor = Executors.newCachedThreadPool(); + final Timer timer = new Timer(executor); + final EventLoop eventLoop = EventLoop.get(); + + final StandaloneCircuitBreakerMaintenance cbMaintenance = new StandaloneCircuitBreakerMaintenance(); + } + + static class DependenciesHolder { + static final Dependencies INSTANCE = new Dependencies(); + } + + @Override + public boolean applies() { + return true; + } + + @Override + public int priority() { + return 0; + } + + @Override + public FaultTolerance.Builder newBuilder(Function, R> finisher) { + Dependencies deps = DependenciesHolder.INSTANCE; + return new StandaloneFaultTolerance.BuilderImpl<>(deps.ftEnabled, deps.executor, deps.timer, deps.eventLoop, + deps.cbMaintenance, false, finisher); + } + + @Override + public FaultTolerance.Builder, R> newAsyncBuilder( + Function>, R> finisher) { + Dependencies deps = DependenciesHolder.INSTANCE; + return new StandaloneFaultTolerance.BuilderImpl<>(deps.ftEnabled, deps.executor, deps.timer, deps.eventLoop, + deps.cbMaintenance, true, finisher); + } + + @Override + public CircuitBreakerMaintenance circuitBreakerMaintenance() { + return DependenciesHolder.INSTANCE.cbMaintenance; + } +} 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 new file mode 100644 index 00000000..8977e9bf --- /dev/null +++ b/implementation/standalone/src/main/resources/META-INF/services/io.smallrye.faulttolerance.api.FaultToleranceSpi @@ -0,0 +1 @@ +io.smallrye.faulttolerance.standalone.StandaloneFaultToleranceSpi \ No newline at end of file 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 new file mode 100644 index 00000000..a5eac2f5 --- /dev/null +++ b/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneBulkheadAsyncTest.java @@ -0,0 +1,45 @@ +package io.smallrye.faulttolerance.standalone.test; + +import static io.smallrye.faulttolerance.core.util.CompletionStages.completedStage; +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.concurrent.CompletionStage; +import java.util.concurrent.TimeUnit; + +import org.eclipse.microprofile.faulttolerance.exceptions.BulkheadException; +import org.junit.jupiter.api.Test; + +import io.smallrye.faulttolerance.api.FaultTolerance; +import io.smallrye.faulttolerance.core.util.party.Party; + +public class StandaloneBulkheadAsyncTest { + @Test + public void asyncBulkhead() throws Exception { + FaultTolerance> guarded = FaultTolerance. createAsync() + .withBulkhead().limit(5).queueSize(5).done() + .withFallback().handler(this::fallback).applyOn(BulkheadException.class).done() + .withThreadOffload(true) + .build(); + + Party party = Party.create(5); + + for (int i = 0; i < 10; i++) { + guarded.call(() -> { + party.participant().attend(); + return completedStage("ignored"); + }); + } + + party.organizer().waitForAll(); + + assertThat(guarded.call(() -> completedStage("value"))) + .succeedsWithin(10, TimeUnit.SECONDS) + .isEqualTo("fallback"); + + party.organizer().disband(); + } + + public CompletionStage fallback() { + return completedStage("fallback"); + } +} 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 new file mode 100644 index 00000000..6e347bfa --- /dev/null +++ b/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneBulkheadTest.java @@ -0,0 +1,59 @@ +package io.smallrye.faulttolerance.standalone.test; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +import org.eclipse.microprofile.faulttolerance.exceptions.BulkheadException; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import io.smallrye.faulttolerance.api.FaultTolerance; +import io.smallrye.faulttolerance.core.util.party.Party; + +public class StandaloneBulkheadTest { + private ExecutorService executor; + + @BeforeEach + public void setUp() { + executor = Executors.newFixedThreadPool(5); + } + + @AfterEach + public void tearDown() throws InterruptedException { + executor.shutdownNow(); + executor.awaitTermination(1, TimeUnit.SECONDS); + } + + @Test + public void bulkhead() throws Exception { + FaultTolerance guarded = FaultTolerance. create() + .withBulkhead().limit(5).done() + .withFallback().handler(this::fallback).applyOn(BulkheadException.class).done() + .build(); + + Party party = Party.create(5); + + for (int i = 0; i < 5; i++) { + executor.submit(() -> { + return guarded.call(() -> { + party.participant().attend(); + return "ignored"; + }); + }); + } + + party.organizer().waitForAll(); + + assertThat(guarded.call(() -> "value")).isEqualTo("fallback"); + + party.organizer().disband(); + } + + public String fallback() { + return "fallback"; + } +} 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 new file mode 100644 index 00000000..44b82fd4 --- /dev/null +++ b/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneCircuitBreakerAsyncTest.java @@ -0,0 +1,91 @@ +package io.smallrye.faulttolerance.standalone.test; + +import static io.smallrye.faulttolerance.core.util.CompletionStages.completedStage; +import static io.smallrye.faulttolerance.core.util.CompletionStages.failedStage; +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.concurrent.CompletionStage; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.function.Supplier; + +import org.eclipse.microprofile.faulttolerance.exceptions.CircuitBreakerOpenException; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import io.smallrye.faulttolerance.api.FaultTolerance; +import io.smallrye.faulttolerance.core.util.TestException; + +public class StandaloneCircuitBreakerAsyncTest { + @BeforeEach + public void setUp() { + FaultTolerance.circuitBreakerMaintenance().resetAll(); + } + + @Test + public void asyncCircuitBreaker() throws Exception { + Supplier> guarded = FaultTolerance.createAsyncSupplier(this::action) + .withCircuitBreaker().requestVolumeThreshold(4).done() + .withFallback().handler(this::fallback).applyOn(CircuitBreakerOpenException.class).done() + .build(); + + for (int i = 0; i < 4; i++) { + assertThat(guarded.get()) + .failsWithin(10, TimeUnit.SECONDS) + .withThrowableOfType(ExecutionException.class) // caused by AssertJ calling future.get() + .withCauseExactlyInstanceOf(TestException.class); + } + + assertThat(guarded.get()) + .succeedsWithin(10, TimeUnit.SECONDS) + .isEqualTo("fallback"); + } + + @Test + public void asyncCircuitBreakerWithSkipOn() throws Exception { + Supplier> guarded = FaultTolerance.createAsyncSupplier(this::action) + .withCircuitBreaker().requestVolumeThreshold(4).skipOn(TestException.class).done() + .withFallback().handler(this::fallback).applyOn(CircuitBreakerOpenException.class).done() + .build(); + + for (int i = 0; i < 4; i++) { + assertThat(guarded.get()) + .failsWithin(10, TimeUnit.SECONDS) + .withThrowableOfType(ExecutionException.class) // caused by AssertJ calling future.get() + .withCauseExactlyInstanceOf(TestException.class); + } + + assertThat(guarded.get()) + .failsWithin(10, TimeUnit.SECONDS) + .withThrowableOfType(ExecutionException.class) // caused by AssertJ calling future.get() + .withCauseExactlyInstanceOf(TestException.class); + } + + @Test + public void asyncCircuitBreakerWithFailOn() throws Exception { + Supplier> guarded = FaultTolerance.createAsyncSupplier(this::action) + .withCircuitBreaker().requestVolumeThreshold(4).failOn(RuntimeException.class).done() + .withFallback().handler(this::fallback).applyOn(CircuitBreakerOpenException.class).done() + .build(); + + for (int i = 0; i < 4; i++) { + assertThat(guarded.get()) + .failsWithin(10, TimeUnit.SECONDS) + .withThrowableOfType(ExecutionException.class) // caused by AssertJ calling future.get() + .withCauseExactlyInstanceOf(TestException.class); + } + + assertThat(guarded.get()) + .failsWithin(10, TimeUnit.SECONDS) + .withThrowableOfType(ExecutionException.class) // caused by AssertJ calling future.get() + .withCauseExactlyInstanceOf(TestException.class); + } + + public CompletionStage action() { + return failedStage(new TestException()); + } + + public CompletionStage fallback() { + return completedStage("fallback"); + } +} 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 new file mode 100644 index 00000000..4a03053b --- /dev/null +++ b/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneCircuitBreakerTest.java @@ -0,0 +1,70 @@ +package io.smallrye.faulttolerance.standalone.test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; + +import java.util.concurrent.Callable; + +import org.eclipse.microprofile.faulttolerance.exceptions.CircuitBreakerOpenException; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import io.smallrye.faulttolerance.api.FaultTolerance; +import io.smallrye.faulttolerance.core.util.TestException; + +public class StandaloneCircuitBreakerTest { + @BeforeEach + public void setUp() { + FaultTolerance.circuitBreakerMaintenance().resetAll(); + } + + @Test + public void circuitBreaker() throws Exception { + Callable guarded = FaultTolerance.createCallable(this::action) + .withCircuitBreaker().requestVolumeThreshold(4).done() + .withFallback().handler(this::fallback).applyOn(CircuitBreakerOpenException.class).done() + .build(); + + for (int i = 0; i < 4; i++) { + assertThatCode(guarded::call).isExactlyInstanceOf(TestException.class); + } + + assertThat(guarded.call()).isEqualTo("fallback"); + } + + @Test + public void circuitBreakerWithSkipOn() { + Callable guarded = FaultTolerance.createCallable(this::action) + .withCircuitBreaker().requestVolumeThreshold(4).skipOn(TestException.class).done() + .withFallback().handler(this::fallback).applyOn(CircuitBreakerOpenException.class).done() + .build(); + + for (int i = 0; i < 4; i++) { + assertThatCode(guarded::call).isExactlyInstanceOf(TestException.class); + } + + assertThatCode(guarded::call).isExactlyInstanceOf(TestException.class); + } + + @Test + public void circuitBreakerWithFailOn() { + Callable guarded = FaultTolerance.createCallable(this::action) + .withCircuitBreaker().requestVolumeThreshold(4).failOn(RuntimeException.class).done() + .withFallback().handler(this::fallback).applyOn(CircuitBreakerOpenException.class).done() + .build(); + + for (int i = 0; i < 4; i++) { + assertThatCode(guarded::call).isExactlyInstanceOf(TestException.class); + } + + assertThatCode(guarded::call).isExactlyInstanceOf(TestException.class); + } + + public String action() throws TestException { + throw new TestException(); + } + + public String fallback() { + return "fallback"; + } +} 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 new file mode 100644 index 00000000..66c25fb1 --- /dev/null +++ b/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneFallbackAsyncTest.java @@ -0,0 +1,85 @@ +package io.smallrye.faulttolerance.standalone.test; + +import static io.smallrye.faulttolerance.core.util.CompletionStages.completedStage; +import static io.smallrye.faulttolerance.core.util.CompletionStages.failedStage; +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.concurrent.CompletionStage; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.function.Supplier; + +import org.junit.jupiter.api.Test; + +import io.smallrye.faulttolerance.api.FaultTolerance; +import io.smallrye.faulttolerance.core.util.TestException; + +public class StandaloneFallbackAsyncTest { + @Test + public void asyncFallbackWithSupplier() { + Supplier> guarded = FaultTolerance.createAsyncSupplier(this::action) + .withFallback().handler(this::fallback).done() + .build(); + + assertThat(guarded.get()) + .succeedsWithin(10, TimeUnit.SECONDS) + .isEqualTo("fallback"); + } + + @Test + public void asyncFallbackWithFunction() { + Supplier> guarded = FaultTolerance.createAsyncSupplier(this::action) + .withFallback().handler(e -> completedStage(e.getClass().getSimpleName())).done() + .build(); + + assertThat(guarded.get()) + .succeedsWithin(10, TimeUnit.SECONDS) + .isEqualTo("TestException"); + } + + @Test + public void asyncFallbackWithSkipOn() { + Supplier> guarded = FaultTolerance.createAsyncSupplier(this::action) + .withFallback().handler(this::fallback).skipOn(TestException.class).done() + .build(); + + assertThat(guarded.get()) + .failsWithin(10, TimeUnit.SECONDS) + .withThrowableOfType(ExecutionException.class) // caused by AssertJ calling future.get() + .withCauseExactlyInstanceOf(TestException.class); + } + + @Test + public void asyncFallbackWithApplyOn() { + Supplier> guarded = FaultTolerance.createAsyncSupplier(this::action) + .withFallback().handler(this::fallback).applyOn(RuntimeException.class).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(); + + assertThat(guarded.get()) + .failsWithin(10, TimeUnit.SECONDS) + .withThrowableOfType(ExecutionException.class) // caused by AssertJ calling future.get() + .withCauseExactlyInstanceOf(TestException.class); + } + + public CompletionStage action() { + return failedStage(new TestException()); + } + + public CompletionStage fallback() { + return completedStage("fallback"); + } +} 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 new file mode 100644 index 00000000..8e408290 --- /dev/null +++ b/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneFallbackTest.java @@ -0,0 +1,57 @@ +package io.smallrye.faulttolerance.standalone.test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; + +import java.util.concurrent.Callable; + +import org.junit.jupiter.api.Test; + +import io.smallrye.faulttolerance.api.FaultTolerance; +import io.smallrye.faulttolerance.core.util.TestException; + +public class StandaloneFallbackTest { + @Test + public void fallbackWithSupplier() throws Exception { + Callable guarded = FaultTolerance.createCallable(this::action) + .withFallback().handler(this::fallback).done() + .build(); + + assertThat(guarded.call()).isEqualTo("fallback"); + } + + @Test + public void fallbackWithFunction() throws Exception { + Callable guarded = FaultTolerance.createCallable(this::action) + .withFallback().handler(e -> e.getClass().getSimpleName()).done() + .build(); + + assertThat(guarded.call()).isEqualTo("TestException"); + } + + @Test + public void fallbackWithSkipOn() { + Callable guarded = FaultTolerance.createCallable(this::action) + .withFallback().handler(this::fallback).skipOn(TestException.class).done() + .build(); + + assertThatCode(guarded::call).isExactlyInstanceOf(TestException.class); + } + + @Test + public void fallbackWithApplyOn() { + Callable guarded = FaultTolerance.createCallable(this::action) + .withFallback().handler(this::fallback).applyOn(RuntimeException.class).done() + .build(); + + assertThatCode(guarded::call).isExactlyInstanceOf(TestException.class); + } + + public String action() throws TestException { + throw new TestException(); + } + + public String fallback() { + return "fallback"; + } +} 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 new file mode 100644 index 00000000..dafc55e4 --- /dev/null +++ b/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandalonePassthroughAsyncTest.java @@ -0,0 +1,146 @@ +package io.smallrye.faulttolerance.standalone.test; + +import static io.smallrye.faulttolerance.core.util.CompletionStages.completedStage; +import static io.smallrye.faulttolerance.core.util.CompletionStages.failedStage; +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; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.function.Supplier; + +import org.junit.jupiter.api.Test; + +import io.smallrye.faulttolerance.api.FaultTolerance; +import io.smallrye.faulttolerance.core.util.TestException; + +public class StandalonePassthroughAsyncTest { + @Test + public void asyncPassthroughCompletedSuccessfully() throws Exception { + FaultTolerance> guard = FaultTolerance. createAsync().build(); + + assertThat(guard.call(this::completeSuccessfully)) + .succeedsWithin(10, TimeUnit.SECONDS) + .isEqualTo("value"); + } + + @Test + public void asyncPassthroughCompletedExceptionally() throws Exception { + FaultTolerance> guard = FaultTolerance. createAsync().build(); + + assertThat(guard.call(this::completeExceptionally)) + .failsWithin(10, TimeUnit.SECONDS) + .withThrowableOfType(ExecutionException.class) // caused by AssertJ calling future.get() + .withCauseExactlyInstanceOf(TestException.class); + } + + @Test + public void asyncPassthroughThrownException() throws Exception { + FaultTolerance> guard = FaultTolerance. createAsync().build(); + + assertThat(guard.call(this::throwException)) + .failsWithin(10, TimeUnit.SECONDS) + .withThrowableOfType(ExecutionException.class) // caused by AssertJ calling future.get() + .withCauseExactlyInstanceOf(TestException.class); + } + + @Test + public void callableAsyncPassthroughCompletedSuccessfully() throws Exception { + Callable> guard = FaultTolerance.createAsyncCallable(this::completeSuccessfully).build(); + + assertThat(guard.call()) + .succeedsWithin(10, TimeUnit.SECONDS) + .isEqualTo("value"); + } + + @Test + public void callableAsyncPassthroughCompletedExceptionally() throws Exception { + Callable> guard = FaultTolerance.createAsyncCallable(this::completeExceptionally).build(); + + assertThat(guard.call()) + .failsWithin(10, TimeUnit.SECONDS) + .withThrowableOfType(ExecutionException.class) // caused by AssertJ calling future.get() + .withCauseExactlyInstanceOf(TestException.class); + } + + @Test + public void callableAsyncPassthroughThrownException() throws Exception { + Callable> guard = FaultTolerance.createAsyncCallable(this::throwException).build(); + + assertThat(guard.call()) + .failsWithin(10, TimeUnit.SECONDS) + .withThrowableOfType(ExecutionException.class) // caused by AssertJ calling future.get() + .withCauseExactlyInstanceOf(TestException.class); + } + + @Test + public void supplierAsyncPassthroughCompletedSuccessfully() { + Supplier> guard = FaultTolerance.createAsyncSupplier(this::completeSuccessfully).build(); + + assertThat(guard.get()) + .succeedsWithin(10, TimeUnit.SECONDS) + .isEqualTo("value"); + } + + @Test + public void supplierAsyncPassthroughCompletedExceptionally() { + Supplier> guard = FaultTolerance.createAsyncSupplier(this::completeExceptionally).build(); + + assertThat(guard.get()) + .failsWithin(10, TimeUnit.SECONDS) + .withThrowableOfType(ExecutionException.class) // caused by AssertJ calling future.get() + .withCauseExactlyInstanceOf(TestException.class); + } + + @Test + public void supplierAsyncPassthroughThrownException() { + Supplier> guard = FaultTolerance.createAsyncSupplier(this::throwRuntimeException).build(); + + assertThat(guard.get()) + .failsWithin(10, TimeUnit.SECONDS) + .withThrowableOfType(ExecutionException.class) // caused by AssertJ calling future.get() + .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 completedStage("value"); + } + + private CompletionStage completeExceptionally() { + return failedStage(new TestException()); + } + + private CompletionStage throwException() throws TestException { + throw new TestException(); + } + + private CompletionStage throwRuntimeException() { + throw new RuntimeException(new TestException()); + } +} 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 new file mode 100644 index 00000000..b3a5ba95 --- /dev/null +++ b/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandalonePassthroughTest.java @@ -0,0 +1,116 @@ +package io.smallrye.faulttolerance.standalone.test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; + +import java.util.concurrent.Callable; +import java.util.function.Supplier; + +import org.junit.jupiter.api.Test; + +import io.smallrye.faulttolerance.api.FaultTolerance; +import io.smallrye.faulttolerance.core.util.TestException; + +public class StandalonePassthroughTest { + @Test + public void passthroughValue() throws Exception { + FaultTolerance guard = FaultTolerance. create().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(); + + assertThatCode(() -> guard.call(this::throwRuntimeException)) + .isExactlyInstanceOf(RuntimeException.class) + .hasCauseExactlyInstanceOf(TestException.class); + + 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(); + + assertThatCode(() -> guard.call(this::throwException)) + .isExactlyInstanceOf(TestException.class); + } + + @Test + public void callablePassthroughValue() throws Exception { + Callable guard = FaultTolerance.createCallable(this::returnValue).build(); + + assertThat(guard.call()).isEqualTo("value"); + } + + @Test + public void callablePassthroughRuntimeException() { + Callable guard = FaultTolerance.createCallable(this::throwRuntimeException).build(); + + assertThatCode(guard::call) + .isExactlyInstanceOf(RuntimeException.class) + .hasCauseExactlyInstanceOf(TestException.class); + } + + @Test + public void callablePassthroughException() { + Callable guard = FaultTolerance.createCallable(this::throwException).build(); + + assertThatCode(guard::call) + .isExactlyInstanceOf(TestException.class); + } + + @Test + public void supplierPassthroughValue() { + Supplier guard = FaultTolerance.createSupplier(this::returnValue).build(); + + assertThat(guard.get()).isEqualTo("value"); + } + + @Test + public void supplierPassthroughRuntimeException() { + Supplier guard = FaultTolerance.createSupplier(this::throwRuntimeException).build(); + + 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"; + } + + private String throwException() throws TestException { + throw new TestException(); + } + + private String throwRuntimeException() { + throw new RuntimeException(new TestException()); + } +} 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 new file mode 100644 index 00000000..cabb3ea2 --- /dev/null +++ b/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneRetryAsyncTest.java @@ -0,0 +1,90 @@ +package io.smallrye.faulttolerance.standalone.test; + +import static io.smallrye.faulttolerance.core.util.CompletionStages.completedStage; +import static io.smallrye.faulttolerance.core.util.CompletionStages.failedStage; +import static org.assertj.core.api.Assertions.assertThat; + +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; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import io.smallrye.faulttolerance.api.FaultTolerance; +import io.smallrye.faulttolerance.core.util.TestException; + +public class StandaloneRetryAsyncTest { + private final AtomicInteger counter = new AtomicInteger(); + + @BeforeEach + public void setUp() { + counter.set(0); + } + + @Test + public void asyncRetry() { + Supplier> guarded = FaultTolerance.createAsyncSupplier(this::action) + .withRetry().maxRetries(3).done() + .withFallback().handler(this::fallback).applyOn(TestException.class).done() + .build(); + + assertThat(guarded.get()) + .succeedsWithin(10, TimeUnit.SECONDS) + .isEqualTo("fallback"); + assertThat(counter).hasValue(4); // 1 initial invocation + 3 retries + } + + @Test + public void asyncRetryWithAbortOn() { + Supplier> guarded = FaultTolerance.createAsyncSupplier(this::action) + .withRetry().maxRetries(3).abortOn(TestException.class).done() + .withFallback().handler(this::fallback).applyOn(TestException.class).done() + .build(); + + assertThat(guarded.get()) + .succeedsWithin(10, TimeUnit.SECONDS) + .isEqualTo("fallback"); + assertThat(counter).hasValue(1); // 1 initial invocation + } + + @Test + public void asyncRetryWithRetryOn() { + Supplier> guarded = FaultTolerance.createAsyncSupplier(this::action) + .withRetry().maxRetries(3).retryOn(RuntimeException.class).done() + .withFallback().handler(this::fallback).applyOn(TestException.class).done() + .build(); + + assertThat(guarded.get()) + .succeedsWithin(10, TimeUnit.SECONDS) + .isEqualTo("fallback"); + assertThat(counter).hasValue(1); // 1 initial invocation + } + + @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::action) + .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 action() { + counter.incrementAndGet(); + return failedStage(new TestException()); + } + + public CompletionStage fallback() { + return completedStage("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 new file mode 100644 index 00000000..a8fc2bda --- /dev/null +++ b/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneRetryTest.java @@ -0,0 +1,62 @@ +package io.smallrye.faulttolerance.standalone.test; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.concurrent.Callable; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import io.smallrye.faulttolerance.api.FaultTolerance; +import io.smallrye.faulttolerance.core.util.TestException; + +public class StandaloneRetryTest { + private int counter; + + @BeforeEach + public void setUp() { + counter = 0; + } + + @Test + public void retry() throws Exception { + Callable guarded = FaultTolerance.createCallable(this::action) + .withRetry().maxRetries(3).done() + .withFallback().applyOn(TestException.class).handler(this::fallback).done() + .build(); + + assertThat(guarded.call()).isEqualTo("fallback"); + assertThat(counter).isEqualTo(4); // 1 initial invocation + 3 retries + } + + @Test + public void retryWithAbortOn() throws Exception { + Callable guarded = FaultTolerance.createCallable(this::action) + .withRetry().maxRetries(3).abortOn(TestException.class).done() + .withFallback().applyOn(TestException.class).handler(this::fallback).done() + .build(); + + assertThat(guarded.call()).isEqualTo("fallback"); + assertThat(counter).isEqualTo(1); // 1 initial invocation + } + + @Test + public void retryWithRetryOn() throws Exception { + Callable guarded = FaultTolerance.createCallable(this::action) + .withRetry().maxRetries(3).retryOn(RuntimeException.class).done() + .withFallback().applyOn(TestException.class).handler(this::fallback).done() + .build(); + + assertThat(guarded.call()).isEqualTo("fallback"); + assertThat(counter).isEqualTo(1); // 1 initial invocation + } + + public String action() throws TestException { + counter++; + throw new TestException(); + } + + public String fallback() { + return "fallback"; + } +} 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 new file mode 100644 index 00000000..102424ac --- /dev/null +++ b/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneTimeoutAsyncTest.java @@ -0,0 +1,43 @@ +package io.smallrye.faulttolerance.standalone.test; + +import static io.smallrye.faulttolerance.core.util.CompletionStages.completedStage; +import static io.smallrye.faulttolerance.core.util.Timing.timed; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.withinPercentage; + +import java.time.temporal.ChronoUnit; +import java.util.concurrent.Callable; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.TimeUnit; + +import org.eclipse.microprofile.faulttolerance.exceptions.TimeoutException; +import org.junit.jupiter.api.Test; + +import io.smallrye.faulttolerance.api.FaultTolerance; + +public class StandaloneTimeoutAsyncTest { + @Test + public void asyncTimeout() throws Exception { + Callable> guarded = FaultTolerance.createAsyncCallable(this::action) + .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(); + + long time = timed(() -> { + assertThat(guarded.call()) + .succeedsWithin(5, TimeUnit.SECONDS) + .isEqualTo("fallback"); + }); + assertThat(time).isCloseTo(1000, withinPercentage(20)); + } + + public CompletionStage action() throws InterruptedException { + Thread.sleep(10_000); + return completedStage("value"); + } + + public CompletionStage fallback() { + return completedStage("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 new file mode 100644 index 00000000..42fa81c0 --- /dev/null +++ b/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneTimeoutTest.java @@ -0,0 +1,37 @@ +package io.smallrye.faulttolerance.standalone.test; + +import static io.smallrye.faulttolerance.core.util.Timing.timed; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.withinPercentage; + +import java.time.temporal.ChronoUnit; +import java.util.concurrent.Callable; + +import org.eclipse.microprofile.faulttolerance.exceptions.TimeoutException; +import org.junit.jupiter.api.Test; + +import io.smallrye.faulttolerance.api.FaultTolerance; + +public class StandaloneTimeoutTest { + @Test + public void timeout() throws Exception { + Callable guarded = FaultTolerance.createCallable(this::action) + .withTimeout().duration(1000, ChronoUnit.MILLIS).done() + .withFallback().applyOn(TimeoutException.class).handler(this::fallback).done() + .build(); + + long time = timed(() -> { + assertThat(guarded.call()).isEqualTo("fallback"); + }); + assertThat(time).isCloseTo(1000, withinPercentage(20)); + } + + public String action() throws InterruptedException { + Thread.sleep(10_000); + return "value"; + } + + public String fallback() { + return "fallback"; + } +} diff --git a/implementation/standalone/src/test/resources/logging.properties b/implementation/standalone/src/test/resources/logging.properties new file mode 100644 index 00000000..ffc41a8e --- /dev/null +++ b/implementation/standalone/src/test/resources/logging.properties @@ -0,0 +1,5 @@ +handlers = java.util.logging.ConsoleHandler +java.util.logging.ConsoleHandler.level = FINEST +java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter +java.util.logging.SimpleFormatter.format=%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS.%1$tL %4$-5s [%3$s] %5$s%6$s%n +io.smallrye.faulttolerance.level = INFO diff --git a/pom.xml b/pom.xml index 3d0d0d7f..092935b3 100644 --- a/pom.xml +++ b/pom.xml @@ -349,6 +349,17 @@ smallrye-fault-tolerance-vertx ${project.version} + + io.smallrye + smallrye-fault-tolerance-standalone + ${project.version} + + + io.smallrye + smallrye-fault-tolerance-standalone + test-jar + ${project.version} + diff --git a/testsuite/basic/pom.xml b/testsuite/basic/pom.xml index acc6a8a4..88a711d3 100644 --- a/testsuite/basic/pom.xml +++ b/testsuite/basic/pom.xml @@ -39,6 +39,12 @@ test-jar test + + io.smallrye + smallrye-fault-tolerance-standalone + test-jar + test + io.smallrye.config @@ -77,13 +83,13 @@ - org.junit.jupiter - junit-jupiter + org.jboss.weld + weld-junit5 test - org.jboss.weld - weld-junit5 + org.junit.jupiter + junit-jupiter test diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/programmatic/CdiBulkheadAsyncTest.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/programmatic/CdiBulkheadAsyncTest.java new file mode 100644 index 00000000..70a16946 --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/programmatic/CdiBulkheadAsyncTest.java @@ -0,0 +1,8 @@ +package io.smallrye.faulttolerance.programmatic; + +import io.smallrye.faulttolerance.standalone.test.StandaloneBulkheadAsyncTest; +import io.smallrye.faulttolerance.util.FaultToleranceBasicTest; + +@FaultToleranceBasicTest +public class CdiBulkheadAsyncTest extends StandaloneBulkheadAsyncTest { +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/programmatic/CdiBulkheadTest.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/programmatic/CdiBulkheadTest.java new file mode 100644 index 00000000..e416b043 --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/programmatic/CdiBulkheadTest.java @@ -0,0 +1,8 @@ +package io.smallrye.faulttolerance.programmatic; + +import io.smallrye.faulttolerance.standalone.test.StandaloneBulkheadTest; +import io.smallrye.faulttolerance.util.FaultToleranceBasicTest; + +@FaultToleranceBasicTest +public class CdiBulkheadTest extends StandaloneBulkheadTest { +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/programmatic/CdiCircuitBreakerAsyncTest.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/programmatic/CdiCircuitBreakerAsyncTest.java new file mode 100644 index 00000000..c73e57b6 --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/programmatic/CdiCircuitBreakerAsyncTest.java @@ -0,0 +1,8 @@ +package io.smallrye.faulttolerance.programmatic; + +import io.smallrye.faulttolerance.standalone.test.StandaloneCircuitBreakerAsyncTest; +import io.smallrye.faulttolerance.util.FaultToleranceBasicTest; + +@FaultToleranceBasicTest +public class CdiCircuitBreakerAsyncTest extends StandaloneCircuitBreakerAsyncTest { +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/programmatic/CdiCircuitBreakerTest.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/programmatic/CdiCircuitBreakerTest.java new file mode 100644 index 00000000..d98dd7fe --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/programmatic/CdiCircuitBreakerTest.java @@ -0,0 +1,8 @@ +package io.smallrye.faulttolerance.programmatic; + +import io.smallrye.faulttolerance.standalone.test.StandaloneCircuitBreakerTest; +import io.smallrye.faulttolerance.util.FaultToleranceBasicTest; + +@FaultToleranceBasicTest +public class CdiCircuitBreakerTest extends StandaloneCircuitBreakerTest { +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/programmatic/CdiFallbackAsyncTest.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/programmatic/CdiFallbackAsyncTest.java new file mode 100644 index 00000000..8eb2088c --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/programmatic/CdiFallbackAsyncTest.java @@ -0,0 +1,8 @@ +package io.smallrye.faulttolerance.programmatic; + +import io.smallrye.faulttolerance.standalone.test.StandaloneFallbackAsyncTest; +import io.smallrye.faulttolerance.util.FaultToleranceBasicTest; + +@FaultToleranceBasicTest +public class CdiFallbackAsyncTest extends StandaloneFallbackAsyncTest { +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/programmatic/CdiFallbackTest.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/programmatic/CdiFallbackTest.java new file mode 100644 index 00000000..9caa70e0 --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/programmatic/CdiFallbackTest.java @@ -0,0 +1,8 @@ +package io.smallrye.faulttolerance.programmatic; + +import io.smallrye.faulttolerance.standalone.test.StandaloneFallbackTest; +import io.smallrye.faulttolerance.util.FaultToleranceBasicTest; + +@FaultToleranceBasicTest +public class CdiFallbackTest extends StandaloneFallbackTest { +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/programmatic/CdiPassthroughAsyncTest.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/programmatic/CdiPassthroughAsyncTest.java new file mode 100644 index 00000000..f2eaa86d --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/programmatic/CdiPassthroughAsyncTest.java @@ -0,0 +1,8 @@ +package io.smallrye.faulttolerance.programmatic; + +import io.smallrye.faulttolerance.standalone.test.StandalonePassthroughAsyncTest; +import io.smallrye.faulttolerance.util.FaultToleranceBasicTest; + +@FaultToleranceBasicTest +public class CdiPassthroughAsyncTest extends StandalonePassthroughAsyncTest { +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/programmatic/CdiPassthroughTest.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/programmatic/CdiPassthroughTest.java new file mode 100644 index 00000000..374fbe92 --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/programmatic/CdiPassthroughTest.java @@ -0,0 +1,8 @@ +package io.smallrye.faulttolerance.programmatic; + +import io.smallrye.faulttolerance.standalone.test.StandalonePassthroughTest; +import io.smallrye.faulttolerance.util.FaultToleranceBasicTest; + +@FaultToleranceBasicTest +public class CdiPassthroughTest extends StandalonePassthroughTest { +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/programmatic/CdiRetryAsyncTest.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/programmatic/CdiRetryAsyncTest.java new file mode 100644 index 00000000..6e1aab78 --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/programmatic/CdiRetryAsyncTest.java @@ -0,0 +1,8 @@ +package io.smallrye.faulttolerance.programmatic; + +import io.smallrye.faulttolerance.standalone.test.StandaloneRetryAsyncTest; +import io.smallrye.faulttolerance.util.FaultToleranceBasicTest; + +@FaultToleranceBasicTest +public class CdiRetryAsyncTest extends StandaloneRetryAsyncTest { +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/programmatic/CdiRetryTest.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/programmatic/CdiRetryTest.java new file mode 100644 index 00000000..cef79222 --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/programmatic/CdiRetryTest.java @@ -0,0 +1,8 @@ +package io.smallrye.faulttolerance.programmatic; + +import io.smallrye.faulttolerance.standalone.test.StandaloneRetryTest; +import io.smallrye.faulttolerance.util.FaultToleranceBasicTest; + +@FaultToleranceBasicTest +public class CdiRetryTest extends StandaloneRetryTest { +} 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 new file mode 100644 index 00000000..2f18b4b4 --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/programmatic/CdiSkipFaultToleranceTest.java @@ -0,0 +1,46 @@ +package io.smallrye.faulttolerance.programmatic; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.concurrent.Callable; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junitpioneer.jupiter.SetSystemProperty; + +import io.smallrye.faulttolerance.api.FaultTolerance; +import io.smallrye.faulttolerance.core.util.TestException; +import io.smallrye.faulttolerance.util.FaultToleranceBasicTest; + +// we boot a new Weld container for each test, so the config property will be read again +// this is impossible for the standalone implementation (unless we forked the JVM for each test) +@FaultToleranceBasicTest +@SetSystemProperty(key = "MP_Fault_Tolerance_NonFallback_Enabled", value = "false") +public class CdiSkipFaultToleranceTest { + private int counter; + + @BeforeEach + public void setUp() { + counter = 0; + } + + @Test + public void skipFaultTolerance() throws Exception { + Callable guarded = FaultTolerance.createCallable(this::action) + .withRetry().maxRetries(3).done() + .withFallback().handler(this::fallback).done() + .build(); + + assertThat(guarded.call()).isEqualTo("fallback"); + assertThat(counter).isEqualTo(1); // 1 initial invocation, no retries + } + + public String action() throws TestException { + counter++; + throw new TestException(); + } + + public String fallback() { + return "fallback"; + } +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/programmatic/CdiTimeoutAsyncTest.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/programmatic/CdiTimeoutAsyncTest.java new file mode 100644 index 00000000..1208dcc8 --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/programmatic/CdiTimeoutAsyncTest.java @@ -0,0 +1,8 @@ +package io.smallrye.faulttolerance.programmatic; + +import io.smallrye.faulttolerance.standalone.test.StandaloneTimeoutAsyncTest; +import io.smallrye.faulttolerance.util.FaultToleranceBasicTest; + +@FaultToleranceBasicTest +public class CdiTimeoutAsyncTest extends StandaloneTimeoutAsyncTest { +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/programmatic/CdiTimeoutTest.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/programmatic/CdiTimeoutTest.java new file mode 100644 index 00000000..cf9b3e6b --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/programmatic/CdiTimeoutTest.java @@ -0,0 +1,8 @@ +package io.smallrye.faulttolerance.programmatic; + +import io.smallrye.faulttolerance.standalone.test.StandaloneTimeoutTest; +import io.smallrye.faulttolerance.util.FaultToleranceBasicTest; + +@FaultToleranceBasicTest +public class CdiTimeoutTest extends StandaloneTimeoutTest { +} From b8b076cf461e5a4a3b437715f8ffd9f20dcf1087 Mon Sep 17 00:00:00 2001 From: Ladislav Thon Date: Tue, 1 Feb 2022 13:17:06 +0100 Subject: [PATCH 08/68] revamp async types support This commit moves from SmallRye Reactive Converters to a built-in async type conversion facility. This new conversion library enforces that the async type is not created from a `CompletionStage` (which is eager and hence already running), but from a `Supplier` of `CompletionStage`. Hence, lazy async types may be created as truly lazy, and if they allow resubscription, that works properly. Tests for resubscription are added both for Mutiny and RxJava 3. SmallRye Reactive Converters supported Mutiny, all RxJava major versions, and Reactor. The built-in library supports Mutiny and RxJava 3, which I believe is enough. Adding support for Reactor would be trivial, but doesn't seem necessary at this point. --- doc/modules/ROOT/pages/integration/intro.adoc | 3 +- doc/modules/ROOT/pages/usage/extra.adoc | 12 ++-- .../core/async/types/AsyncTypeConverter.java | 12 ++++ .../async/types}/AsyncTypesConversion.java | 28 ++++---- .../core/async/types/AsyncTypesLogger.java | 10 +++ .../async/types/CompletionStageConverter.java | 21 ++++++ ...erance.core.async.types.AsyncTypeConverter | 1 + implementation/fault-tolerance/pom.xml | 4 -- .../smallrye/faulttolerance/AsyncTypes.java | 69 +++---------------- .../FaultToleranceInterceptor.java | 4 +- .../config/AsyncValidation.java | 4 +- implementation/mutiny/pom.xml | 26 +++++++ .../mutiny/impl/UniConverter.java | 24 +++++++ ...erance.core.async.types.AsyncTypeConverter | 1 + implementation/pom.xml | 5 +- implementation/rxjava3/pom.xml | 26 +++++++ .../rxjava3/impl/CompletableConverter.java | 24 +++++++ .../rxjava3/impl/MaybeConverter.java | 24 +++++++ .../rxjava3/impl/SingleConverter.java | 24 +++++++ ...erance.core.async.types.AsyncTypeConverter | 3 + implementation/vertx/pom.xml | 4 -- pom.xml | 31 +++++++-- testsuite/basic/pom.xml | 25 +++---- .../mutiny/resubscription/HelloService.java | 22 ++++++ .../MutinyResubscriptionTest.java | 25 +++++++ .../rxjava/resubscription/HelloService.java | 22 ++++++ .../RxjavaResubscriptionTest.java | 25 +++++++ 27 files changed, 361 insertions(+), 118 deletions(-) create mode 100644 implementation/core/src/main/java/io/smallrye/faulttolerance/core/async/types/AsyncTypeConverter.java rename implementation/{fault-tolerance/src/main/java/io/smallrye/faulttolerance/internal => core/src/main/java/io/smallrye/faulttolerance/core/async/types}/AsyncTypesConversion.java (66%) create mode 100644 implementation/core/src/main/java/io/smallrye/faulttolerance/core/async/types/AsyncTypesLogger.java create mode 100644 implementation/core/src/main/java/io/smallrye/faulttolerance/core/async/types/CompletionStageConverter.java create mode 100644 implementation/core/src/main/resources/META-INF/services/io.smallrye.faulttolerance.core.async.types.AsyncTypeConverter create mode 100644 implementation/mutiny/pom.xml create mode 100644 implementation/mutiny/src/main/java/io/smallrye/faulttolerance/mutiny/impl/UniConverter.java create mode 100644 implementation/mutiny/src/main/resources/META-INF/services/io.smallrye.faulttolerance.core.async.types.AsyncTypeConverter create mode 100644 implementation/rxjava3/pom.xml create mode 100644 implementation/rxjava3/src/main/java/io/smallrye/faulttolerance/rxjava3/impl/CompletableConverter.java create mode 100644 implementation/rxjava3/src/main/java/io/smallrye/faulttolerance/rxjava3/impl/MaybeConverter.java create mode 100644 implementation/rxjava3/src/main/java/io/smallrye/faulttolerance/rxjava3/impl/SingleConverter.java create mode 100644 implementation/rxjava3/src/main/resources/META-INF/services/io.smallrye.faulttolerance.core.async.types.AsyncTypeConverter create mode 100644 testsuite/basic/src/test/java/io/smallrye/faulttolerance/async/types/mutiny/resubscription/HelloService.java create mode 100644 testsuite/basic/src/test/java/io/smallrye/faulttolerance/async/types/mutiny/resubscription/MutinyResubscriptionTest.java create mode 100644 testsuite/basic/src/test/java/io/smallrye/faulttolerance/async/types/rxjava/resubscription/HelloService.java create mode 100644 testsuite/basic/src/test/java/io/smallrye/faulttolerance/async/types/rxjava/resubscription/RxjavaResubscriptionTest.java diff --git a/doc/modules/ROOT/pages/integration/intro.adoc b/doc/modules/ROOT/pages/integration/intro.adoc index 293d6b60..b10b2df4 100644 --- a/doc/modules/ROOT/pages/integration/intro.adoc +++ b/doc/modules/ROOT/pages/integration/intro.adoc @@ -14,7 +14,6 @@ These artifacts must be always present, together with the following dependencies * `org.eclipse.microprofile.fault-tolerance:microprofile-fault-tolerance-api`; * `org.eclipse.microprofile.config:microprofile-config-api` and some implementation; * `org.eclipse.microprofile.metrics:microprofile-metrics-api` and some implementation (implementation is only required if fault tolerance metrics are enabled); -* `io.smallrye.reactive:smallrye-reactive-converter-api`; * `org.jboss.logging:jboss-logging`. Some other artifacts are provided to facilitate additional integrations. @@ -33,4 +32,4 @@ The remaining integration concerns are described in the following sections: A completely standalone implementation of the programmatic API exists in the `io.smallrye:smallrye-fault-tolerance-standalone` artifact. This is supposed to be used in a non-CDI environment. -Runtimes that integrate {smallrye-fault-tolerance} typically do so using CDI, and so should ignore this artifact. \ No newline at end of file +Runtimes that integrate {smallrye-fault-tolerance} typically do so using CDI, and so should ignore this artifact. diff --git a/doc/modules/ROOT/pages/usage/extra.adoc b/doc/modules/ROOT/pages/usage/extra.adoc index 67433f0a..6db15491 100644 --- a/doc/modules/ROOT/pages/usage/extra.adoc +++ b/doc/modules/ROOT/pages/usage/extra.adoc @@ -175,10 +175,9 @@ We also recommend avoiding `@Asynchronous` methods that return `Future`, because * Mutiny: `Uni` * RxJava: `Single`, `Maybe`, `Completable` -* Reactor: `Mono` These types are treated just like `CompletionStage`, so everything that works for `CompletionStage` works for these types as well. -Stream-like types (`Multi`, `Observable`, `Flowable`, `Flux`) are not supported, because their semantics can't be easily expressed in terms of `CompletionStage`. +Stream-like types (`Multi`, `Observable`, `Flowable`) are not supported, because their semantics can't be easily expressed in terms of `CompletionStage`. For example: @@ -198,16 +197,13 @@ public class MyService { <2> Returning the `Uni` type from Mutiny. This shows that whatever works for `CompletionStage` also works for the other async types. -The implementation is based on the https://github.com/smallrye/smallrye-reactive-utils/tree/main/reactive-converters[SmallRye Reactive Converters] project. +The implementation internally converts the async types to a `CompletionStage` and back. This means that to be able to use any particular asynchronous type, the corresponding converter library must be present. It is possible that the runtime you use already provides the correct integration. Otherwise, add a dependency to your application: -* https://smallrye.io/smallrye-mutiny/[Mutiny]: `io.smallrye.reactive:smallrye-reactive-converter-mutiny` -* https://github.com/ReactiveX/RxJava/tree/1.x[RxJava 1]: `io.smallrye.reactive:smallrye-reactive-converter-rxjava1` -* https://github.com/ReactiveX/RxJava/tree/2.x[RxJava 2]: `io.smallrye.reactive:smallrye-reactive-converter-rxjava2` -* https://github.com/ReactiveX/RxJava/tree/3.x[RxJava 3]: `io.smallrye.reactive:smallrye-reactive-converter-rxjava3` -* https://projectreactor.io/[Reactor]: `io.smallrye.reactive:smallrye-reactive-converter-reactor` +* https://smallrye.io/smallrye-mutiny/[Mutiny]: `io.smallrye:smallrye-fault-tolerance-mutiny` +* https://github.com/ReactiveX/RxJava/tree/3.x[RxJava 3]: `io.smallrye:smallrye-fault-tolerance-rxjava3` .Quarkus **** diff --git a/implementation/core/src/main/java/io/smallrye/faulttolerance/core/async/types/AsyncTypeConverter.java b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/async/types/AsyncTypeConverter.java new file mode 100644 index 00000000..98a02562 --- /dev/null +++ b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/async/types/AsyncTypeConverter.java @@ -0,0 +1,12 @@ +package io.smallrye.faulttolerance.core.async.types; + +import java.util.concurrent.CompletionStage; +import java.util.function.Supplier; + +public interface AsyncTypeConverter { + Class type(); + + AT fromCompletionStage(Supplier> completionStageSupplier); + + CompletionStage toCompletionStage(AT asyncValue); +} diff --git a/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/internal/AsyncTypesConversion.java b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/async/types/AsyncTypesConversion.java similarity index 66% rename from implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/internal/AsyncTypesConversion.java rename to implementation/core/src/main/java/io/smallrye/faulttolerance/core/async/types/AsyncTypesConversion.java index 4af9459d..9b779b92 100644 --- a/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/internal/AsyncTypesConversion.java +++ b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/async/types/AsyncTypesConversion.java @@ -1,20 +1,18 @@ -package io.smallrye.faulttolerance.internal; +package io.smallrye.faulttolerance.core.async.types; -import static io.smallrye.faulttolerance.internal.InternalLogger.LOG; - -import java.util.concurrent.CompletionStage; +import static io.smallrye.faulttolerance.core.async.types.AsyncTypesLogger.LOG; +import static io.smallrye.faulttolerance.core.util.SneakyThrow.sneakyThrow; import io.smallrye.faulttolerance.core.FaultToleranceStrategy; import io.smallrye.faulttolerance.core.InvocationContext; -import io.smallrye.reactive.converters.ReactiveTypeConverter; @SuppressWarnings({ "rawtypes", "unchecked" }) public class AsyncTypesConversion { public static class ToCompletionStage implements FaultToleranceStrategy { private final FaultToleranceStrategy delegate; - private final ReactiveTypeConverter converter; + private final AsyncTypeConverter converter; - public ToCompletionStage(FaultToleranceStrategy delegate, ReactiveTypeConverter converter) { + public ToCompletionStage(FaultToleranceStrategy delegate, AsyncTypeConverter converter) { this.delegate = delegate; this.converter = converter; } @@ -23,8 +21,7 @@ public ToCompletionStage(FaultToleranceStrategy delegate, ReactiveTypeConverter public Object apply(InvocationContext ctx) throws Exception { LOG.trace("AsyncTypesConversion.ToCompletionStage started"); try { - Object result = delegate.apply(ctx); - return converter.toCompletionStage(result); + return converter.toCompletionStage(delegate.apply(ctx)); } finally { LOG.trace("AsyncTypesConversion.ToCompletionStage finished"); } @@ -33,9 +30,9 @@ public Object apply(InvocationContext ctx) throws Exception { public static class FromCompletionStage implements FaultToleranceStrategy { private final FaultToleranceStrategy delegate; - private final ReactiveTypeConverter converter; + private final AsyncTypeConverter converter; - public FromCompletionStage(FaultToleranceStrategy delegate, ReactiveTypeConverter converter) { + public FromCompletionStage(FaultToleranceStrategy delegate, AsyncTypeConverter converter) { this.delegate = delegate; this.converter = converter; } @@ -44,8 +41,13 @@ public FromCompletionStage(FaultToleranceStrategy delegate, ReactiveTypeConverte public Object apply(InvocationContext ctx) throws Exception { LOG.trace("AsyncTypesConversion.FromCompletionStage started"); try { - CompletionStage result = (CompletionStage) delegate.apply(ctx); - return converter.fromCompletionStage(result); + return converter.fromCompletionStage(() -> { + try { + return delegate.apply(ctx); + } catch (Exception e) { + throw sneakyThrow(e); + } + }); } finally { LOG.trace("AsyncTypesConversion.FromCompletionStage finished"); } diff --git a/implementation/core/src/main/java/io/smallrye/faulttolerance/core/async/types/AsyncTypesLogger.java b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/async/types/AsyncTypesLogger.java new file mode 100644 index 00000000..c8a45fe9 --- /dev/null +++ b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/async/types/AsyncTypesLogger.java @@ -0,0 +1,10 @@ +package io.smallrye.faulttolerance.core.async.types; + +import org.jboss.logging.BasicLogger; +import org.jboss.logging.Logger; +import org.jboss.logging.annotations.MessageLogger; + +@MessageLogger(projectCode = "SRFTL", length = 5) +interface AsyncTypesLogger extends BasicLogger { + AsyncTypesLogger LOG = Logger.getMessageLogger(AsyncTypesLogger.class, AsyncTypesLogger.class.getPackage().getName()); +} diff --git a/implementation/core/src/main/java/io/smallrye/faulttolerance/core/async/types/CompletionStageConverter.java b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/async/types/CompletionStageConverter.java new file mode 100644 index 00000000..09c89dff --- /dev/null +++ b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/async/types/CompletionStageConverter.java @@ -0,0 +1,21 @@ +package io.smallrye.faulttolerance.core.async.types; + +import java.util.concurrent.CompletionStage; +import java.util.function.Supplier; + +public class CompletionStageConverter implements AsyncTypeConverter> { + @Override + public Class type() { + return CompletionStage.class; + } + + @Override + public CompletionStage fromCompletionStage(Supplier> completionStageSupplier) { + return completionStageSupplier.get(); + } + + @Override + public CompletionStage toCompletionStage(CompletionStage asyncValue) { + return asyncValue; + } +} diff --git a/implementation/core/src/main/resources/META-INF/services/io.smallrye.faulttolerance.core.async.types.AsyncTypeConverter b/implementation/core/src/main/resources/META-INF/services/io.smallrye.faulttolerance.core.async.types.AsyncTypeConverter new file mode 100644 index 00000000..3502be72 --- /dev/null +++ b/implementation/core/src/main/resources/META-INF/services/io.smallrye.faulttolerance.core.async.types.AsyncTypeConverter @@ -0,0 +1 @@ +io.smallrye.faulttolerance.core.async.types.CompletionStageConverter \ No newline at end of file diff --git a/implementation/fault-tolerance/pom.xml b/implementation/fault-tolerance/pom.xml index a424b66f..816ad635 100644 --- a/implementation/fault-tolerance/pom.xml +++ b/implementation/fault-tolerance/pom.xml @@ -73,10 +73,6 @@ org.eclipse.microprofile.metrics microprofile-metrics-api - - io.smallrye.reactive - smallrye-reactive-converter-api - io.opentracing opentracing-api diff --git a/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/AsyncTypes.java b/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/AsyncTypes.java index 202a4a2d..eee62b8a 100644 --- a/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/AsyncTypes.java +++ b/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/AsyncTypes.java @@ -2,27 +2,20 @@ import java.util.Collection; import java.util.Collections; -import java.util.LinkedHashMap; +import java.util.HashMap; import java.util.Map; import java.util.ServiceLoader; -import java.util.concurrent.CompletionStage; -import org.reactivestreams.Publisher; - -import io.smallrye.reactive.converters.ReactiveTypeConverter; +import io.smallrye.faulttolerance.core.async.types.AsyncTypeConverter; // TODO reduce statics? public class AsyncTypes { - private static final Map, ReactiveTypeConverter> registry; + private static final Map, AsyncTypeConverter> registry; static { - Map, ReactiveTypeConverter> map = new LinkedHashMap<>(); - // include CompletionStage to be able to handle all async types uniformly - map.put(CompletionStage.class, new CompletionStageConverter()); - for (ReactiveTypeConverter converter : ServiceLoader.load(ReactiveTypeConverter.class)) { - if (converter.emitAtMostOneItem() || !converter.emitItems()) { - map.put(converter.type(), converter); - } + Map, AsyncTypeConverter> map = new HashMap<>(); + for (AsyncTypeConverter converter : ServiceLoader.load(AsyncTypeConverter.class)) { + map.put(converter.type(), converter); } registry = Collections.unmodifiableMap(map); } @@ -31,13 +24,13 @@ public static boolean isKnown(Class type) { return registry.containsKey(type); } - public static ReactiveTypeConverter get(Class type) { + public static AsyncTypeConverter get(Class type) { return registry.get(type); } @SuppressWarnings({ "rawtypes", "unchecked" }) public static T toCompletionStageIfRequired(Object value, Class type) { - ReactiveTypeConverter converter = registry.get(type); + AsyncTypeConverter converter = registry.get(type); if (converter != null) { return (T) converter.toCompletionStage(value); } else { @@ -45,51 +38,7 @@ public static T toCompletionStageIfRequired(Object value, Class type) { } } - public static Collection> allKnown() { + public static Collection> allKnown() { return registry.values(); } - - @SuppressWarnings("rawtypes") - private static class CompletionStageConverter implements ReactiveTypeConverter { - @SuppressWarnings("unchecked") - @Override - public CompletionStage toCompletionStage(CompletionStage instance) { - return instance; - } - - @Override - public Publisher toRSPublisher(CompletionStage instance) { - throw new UnsupportedOperationException(); - } - - @Override - public CompletionStage fromCompletionStage(CompletionStage cs) { - return cs; - } - - @Override - public CompletionStage fromPublisher(Publisher publisher) { - throw new UnsupportedOperationException(); - } - - @Override - public Class type() { - return CompletionStage.class; - } - - @Override - public boolean emitItems() { - return true; - } - - @Override - public boolean emitAtMostOneItem() { - return true; - } - - @Override - public boolean supportNullValue() { - return true; - } - } } 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 334731bc..477f03ac 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,6 +51,7 @@ import io.smallrye.faulttolerance.core.async.CompletionStageExecution; import io.smallrye.faulttolerance.core.async.FutureExecution; import io.smallrye.faulttolerance.core.async.RememberEventLoop; +import io.smallrye.faulttolerance.core.async.types.AsyncTypesConversion; import io.smallrye.faulttolerance.core.bulkhead.CompletionStageThreadPoolBulkhead; import io.smallrye.faulttolerance.core.bulkhead.FutureThreadPoolBulkhead; import io.smallrye.faulttolerance.core.bulkhead.SemaphoreBulkhead; @@ -84,7 +85,6 @@ import io.smallrye.faulttolerance.core.util.DirectExecutor; import io.smallrye.faulttolerance.core.util.ExceptionDecision; import io.smallrye.faulttolerance.core.util.SetOfThrowables; -import io.smallrye.faulttolerance.internal.AsyncTypesConversion; import io.smallrye.faulttolerance.internal.InterceptionPoint; import io.smallrye.faulttolerance.internal.RequestScopeActivator; import io.smallrye.faulttolerance.internal.StrategyCache; @@ -174,7 +174,7 @@ private Object asyncFlow(FaultToleranceOperation operation, InvocationContext in try { return strategy.apply(ctx); } catch (Exception e) { - return AsyncTypes.get(operation.getReturnType()).fromCompletionStage(failedStage(e)); + return AsyncTypes.get(operation.getReturnType()).fromCompletionStage(() -> failedStage(e)); } } diff --git a/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/config/AsyncValidation.java b/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/config/AsyncValidation.java index f6a75770..7999b2a3 100644 --- a/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/config/AsyncValidation.java +++ b/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/config/AsyncValidation.java @@ -4,7 +4,7 @@ import java.util.concurrent.Future; import io.smallrye.faulttolerance.AsyncTypes; -import io.smallrye.reactive.converters.ReactiveTypeConverter; +import io.smallrye.faulttolerance.core.async.types.AsyncTypeConverter; final class AsyncValidation { static boolean isAcceptableReturnType(Class returnType) { @@ -13,7 +13,7 @@ static boolean isAcceptableReturnType(Class returnType) { static String describeKnownAsyncTypes() { StringJoiner result = new StringJoiner(" or "); - for (ReactiveTypeConverter converter : AsyncTypes.allKnown()) { + for (AsyncTypeConverter converter : AsyncTypes.allKnown()) { result.add(converter.type().getName()); } return result.toString(); diff --git a/implementation/mutiny/pom.xml b/implementation/mutiny/pom.xml new file mode 100644 index 00000000..79044683 --- /dev/null +++ b/implementation/mutiny/pom.xml @@ -0,0 +1,26 @@ + + + 4.0.0 + + + io.smallrye + smallrye-fault-tolerance-implementation-parent + 5.2.2-SNAPSHOT + + + smallrye-fault-tolerance-mutiny + + SmallRye Fault Tolerance: Mutiny Integration + + + + io.smallrye + smallrye-fault-tolerance-core + + + + io.smallrye.reactive + mutiny + + + diff --git a/implementation/mutiny/src/main/java/io/smallrye/faulttolerance/mutiny/impl/UniConverter.java b/implementation/mutiny/src/main/java/io/smallrye/faulttolerance/mutiny/impl/UniConverter.java new file mode 100644 index 00000000..7043e92e --- /dev/null +++ b/implementation/mutiny/src/main/java/io/smallrye/faulttolerance/mutiny/impl/UniConverter.java @@ -0,0 +1,24 @@ +package io.smallrye.faulttolerance.mutiny.impl; + +import java.util.concurrent.CompletionStage; +import java.util.function.Supplier; + +import io.smallrye.faulttolerance.core.async.types.AsyncTypeConverter; +import io.smallrye.mutiny.Uni; + +public class UniConverter implements AsyncTypeConverter> { + @Override + public Class type() { + return Uni.class; + } + + @Override + public Uni fromCompletionStage(Supplier> completionStageSupplier) { + return Uni.createFrom().completionStage(completionStageSupplier); + } + + @Override + public CompletionStage toCompletionStage(Uni uni) { + return uni.subscribeAsCompletionStage(); + } +} diff --git a/implementation/mutiny/src/main/resources/META-INF/services/io.smallrye.faulttolerance.core.async.types.AsyncTypeConverter b/implementation/mutiny/src/main/resources/META-INF/services/io.smallrye.faulttolerance.core.async.types.AsyncTypeConverter new file mode 100644 index 00000000..c0d8ec02 --- /dev/null +++ b/implementation/mutiny/src/main/resources/META-INF/services/io.smallrye.faulttolerance.core.async.types.AsyncTypeConverter @@ -0,0 +1 @@ +io.smallrye.faulttolerance.mutiny.impl.UniConverter \ No newline at end of file diff --git a/implementation/pom.xml b/implementation/pom.xml index ed8bc142..df5ef1ae 100644 --- a/implementation/pom.xml +++ b/implementation/pom.xml @@ -30,11 +30,14 @@ core + mutiny + rxjava3 + vertx + autoconfig fault-tolerance context-propagation tracing-propagation - vertx standalone diff --git a/implementation/rxjava3/pom.xml b/implementation/rxjava3/pom.xml new file mode 100644 index 00000000..a1091891 --- /dev/null +++ b/implementation/rxjava3/pom.xml @@ -0,0 +1,26 @@ + + + 4.0.0 + + + io.smallrye + smallrye-fault-tolerance-implementation-parent + 5.2.2-SNAPSHOT + + + smallrye-fault-tolerance-rxjava3 + + SmallRye Fault Tolerance: RxJava 3 Integration + + + + io.smallrye + smallrye-fault-tolerance-core + + + + io.reactivex.rxjava3 + rxjava + + + diff --git a/implementation/rxjava3/src/main/java/io/smallrye/faulttolerance/rxjava3/impl/CompletableConverter.java b/implementation/rxjava3/src/main/java/io/smallrye/faulttolerance/rxjava3/impl/CompletableConverter.java new file mode 100644 index 00000000..cf9938c1 --- /dev/null +++ b/implementation/rxjava3/src/main/java/io/smallrye/faulttolerance/rxjava3/impl/CompletableConverter.java @@ -0,0 +1,24 @@ +package io.smallrye.faulttolerance.rxjava3.impl; + +import java.util.concurrent.CompletionStage; +import java.util.function.Supplier; + +import io.reactivex.rxjava3.core.Completable; +import io.smallrye.faulttolerance.core.async.types.AsyncTypeConverter; + +public class CompletableConverter implements AsyncTypeConverter { + @Override + public Class type() { + return Completable.class; + } + + @Override + public Completable fromCompletionStage(Supplier> completionStageSupplier) { + return Completable.defer(() -> Completable.fromCompletionStage(completionStageSupplier.get())); + } + + @Override + public CompletionStage toCompletionStage(Completable completable) { + return completable.toCompletionStage(null); + } +} diff --git a/implementation/rxjava3/src/main/java/io/smallrye/faulttolerance/rxjava3/impl/MaybeConverter.java b/implementation/rxjava3/src/main/java/io/smallrye/faulttolerance/rxjava3/impl/MaybeConverter.java new file mode 100644 index 00000000..e5075b74 --- /dev/null +++ b/implementation/rxjava3/src/main/java/io/smallrye/faulttolerance/rxjava3/impl/MaybeConverter.java @@ -0,0 +1,24 @@ +package io.smallrye.faulttolerance.rxjava3.impl; + +import java.util.concurrent.CompletionStage; +import java.util.function.Supplier; + +import io.reactivex.rxjava3.core.Maybe; +import io.smallrye.faulttolerance.core.async.types.AsyncTypeConverter; + +public class MaybeConverter implements AsyncTypeConverter> { + @Override + public Class type() { + return Maybe.class; + } + + @Override + public Maybe fromCompletionStage(Supplier> completionStageSupplier) { + return Maybe.defer(() -> Maybe.fromCompletionStage(completionStageSupplier.get())); + } + + @Override + public CompletionStage toCompletionStage(Maybe maybe) { + return maybe.toCompletionStage(null); + } +} diff --git a/implementation/rxjava3/src/main/java/io/smallrye/faulttolerance/rxjava3/impl/SingleConverter.java b/implementation/rxjava3/src/main/java/io/smallrye/faulttolerance/rxjava3/impl/SingleConverter.java new file mode 100644 index 00000000..99b9ade1 --- /dev/null +++ b/implementation/rxjava3/src/main/java/io/smallrye/faulttolerance/rxjava3/impl/SingleConverter.java @@ -0,0 +1,24 @@ +package io.smallrye.faulttolerance.rxjava3.impl; + +import java.util.concurrent.CompletionStage; +import java.util.function.Supplier; + +import io.reactivex.rxjava3.core.Single; +import io.smallrye.faulttolerance.core.async.types.AsyncTypeConverter; + +public class SingleConverter implements AsyncTypeConverter> { + @Override + public Class type() { + return Single.class; + } + + @Override + public Single fromCompletionStage(Supplier> completionStageSupplier) { + return Single.defer(() -> Single.fromCompletionStage(completionStageSupplier.get())); + } + + @Override + public CompletionStage toCompletionStage(Single single) { + return single.toCompletionStage(); + } +} diff --git a/implementation/rxjava3/src/main/resources/META-INF/services/io.smallrye.faulttolerance.core.async.types.AsyncTypeConverter b/implementation/rxjava3/src/main/resources/META-INF/services/io.smallrye.faulttolerance.core.async.types.AsyncTypeConverter new file mode 100644 index 00000000..6d843cfb --- /dev/null +++ b/implementation/rxjava3/src/main/resources/META-INF/services/io.smallrye.faulttolerance.core.async.types.AsyncTypeConverter @@ -0,0 +1,3 @@ +io.smallrye.faulttolerance.rxjava3.impl.SingleConverter +io.smallrye.faulttolerance.rxjava3.impl.MaybeConverter +io.smallrye.faulttolerance.rxjava3.impl.CompletableConverter \ No newline at end of file diff --git a/implementation/vertx/pom.xml b/implementation/vertx/pom.xml index a72d778a..8d2a51d4 100644 --- a/implementation/vertx/pom.xml +++ b/implementation/vertx/pom.xml @@ -28,10 +28,6 @@ SmallRye Fault Tolerance: Vert.x Integration - - jakarta.enterprise - jakarta.enterprise.cdi-api - io.smallrye smallrye-fault-tolerance-core diff --git a/pom.xml b/pom.xml index 092935b3..a1811c7f 100644 --- a/pom.xml +++ b/pom.xml @@ -175,8 +175,8 @@ io.smallrye.reactive - smallrye-reactive-converter-api - ${version.smallrye-reactive-utils} + mutiny + ${version.mutiny} io.opentracing @@ -199,6 +199,11 @@ ${version.opentracing} test-jar + + io.reactivex.rxjava3 + rxjava + ${version.rxjava3} + io.vertx vertx-core @@ -319,6 +324,22 @@ test-jar ${project.version} + + io.smallrye + smallrye-fault-tolerance-mutiny + ${project.version} + + + io.smallrye + smallrye-fault-tolerance-rxjava3 + ${project.version} + + + io.smallrye + smallrye-fault-tolerance-vertx + ${project.version} + + io.smallrye smallrye-fault-tolerance-autoconfig-core @@ -344,11 +365,7 @@ smallrye-fault-tolerance-tracing-propagation ${project.version} - - io.smallrye - smallrye-fault-tolerance-vertx - ${project.version} - + io.smallrye smallrye-fault-tolerance-standalone diff --git a/testsuite/basic/pom.xml b/testsuite/basic/pom.xml index 88a711d3..7494a38a 100644 --- a/testsuite/basic/pom.xml +++ b/testsuite/basic/pom.xml @@ -33,6 +33,16 @@ smallrye-fault-tolerance test + + io.smallrye + smallrye-fault-tolerance-mutiny + test + + + io.smallrye + smallrye-fault-tolerance-rxjava3 + test + io.smallrye smallrye-fault-tolerance-core @@ -56,29 +66,14 @@ smallrye-metrics test - io.smallrye.reactive mutiny - ${version.mutiny} test io.reactivex.rxjava3 rxjava - ${version.rxjava3} - test - - - io.smallrye.reactive - smallrye-reactive-converter-mutiny - ${version.smallrye-reactive-utils} - test - - - io.smallrye.reactive - smallrye-reactive-converter-rxjava3 - ${version.smallrye-reactive-utils} test diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/async/types/mutiny/resubscription/HelloService.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/async/types/mutiny/resubscription/HelloService.java new file mode 100644 index 00000000..f75aa69e --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/async/types/mutiny/resubscription/HelloService.java @@ -0,0 +1,22 @@ +package io.smallrye.faulttolerance.async.types.mutiny.resubscription; + +import java.util.concurrent.atomic.AtomicInteger; + +import javax.enterprise.context.ApplicationScoped; + +import org.eclipse.microprofile.faulttolerance.Retry; + +import io.smallrye.common.annotation.NonBlocking; +import io.smallrye.mutiny.Uni; + +@ApplicationScoped +public class HelloService { + static final AtomicInteger COUNTER = new AtomicInteger(0); + + @NonBlocking + @Retry + public Uni hello() { + COUNTER.incrementAndGet(); + return Uni.createFrom().failure(IllegalArgumentException::new); + } +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/async/types/mutiny/resubscription/MutinyResubscriptionTest.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/async/types/mutiny/resubscription/MutinyResubscriptionTest.java new file mode 100644 index 00000000..3fa070c1 --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/async/types/mutiny/resubscription/MutinyResubscriptionTest.java @@ -0,0 +1,25 @@ +package io.smallrye.faulttolerance.async.types.mutiny.resubscription; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.Test; + +import io.smallrye.faulttolerance.util.FaultToleranceBasicTest; +import io.smallrye.mutiny.Uni; + +@FaultToleranceBasicTest +public class MutinyResubscriptionTest { + // this test verifies resubscription, which is triggered via retry + + @Test + public void test(HelloService service) { + Uni hello = service.hello() + .onFailure().retry().atMost(2) + .onFailure().recoverWithItem("hello"); + assertThat(hello.await().indefinitely()).isEqualTo("hello"); + + // the service.hello() method has @Retry with default settings, so 1 initial attempt + 3 retries = 4 total + // the onFailure().retry() handler does 1 initial attempt + 2 retries = 3 total + assertThat(HelloService.COUNTER).hasValue(4 * 3); + } +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/async/types/rxjava/resubscription/HelloService.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/async/types/rxjava/resubscription/HelloService.java new file mode 100644 index 00000000..c025dff2 --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/async/types/rxjava/resubscription/HelloService.java @@ -0,0 +1,22 @@ +package io.smallrye.faulttolerance.async.types.rxjava.resubscription; + +import java.util.concurrent.atomic.AtomicInteger; + +import javax.enterprise.context.ApplicationScoped; + +import org.eclipse.microprofile.faulttolerance.Retry; + +import io.reactivex.rxjava3.core.Maybe; +import io.smallrye.common.annotation.NonBlocking; + +@ApplicationScoped +public class HelloService { + static final AtomicInteger COUNTER = new AtomicInteger(0); + + @NonBlocking + @Retry + public Maybe hello() { + COUNTER.incrementAndGet(); + return Maybe.error(IllegalArgumentException::new); + } +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/async/types/rxjava/resubscription/RxjavaResubscriptionTest.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/async/types/rxjava/resubscription/RxjavaResubscriptionTest.java new file mode 100644 index 00000000..17502f2b --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/async/types/rxjava/resubscription/RxjavaResubscriptionTest.java @@ -0,0 +1,25 @@ +package io.smallrye.faulttolerance.async.types.rxjava.resubscription; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.Test; + +import io.reactivex.rxjava3.core.Maybe; +import io.smallrye.faulttolerance.util.FaultToleranceBasicTest; + +@FaultToleranceBasicTest +public class RxjavaResubscriptionTest { + // this test verifies resubscription, which is triggered via retry + + @Test + public void test(HelloService service) { + Maybe hello = service.hello() + .retry(2) + .onErrorReturnItem("hello"); + assertThat(hello.blockingGet()).isEqualTo("hello"); + + // the service.hello() method has @Retry with default settings, so 1 initial attempt + 3 retries = 4 total + // the retry() handler does 1 initial attempt + 2 retries = 3 total + assertThat(HelloService.COUNTER).hasValue(4 * 3); + } +} From 12c85f59d1557f5d8bb76ba6a0a28374d93a6871 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 3 Feb 2022 07:11:05 +0000 Subject: [PATCH 09/68] Bump smallrye-common-annotation from 1.8.0 to 1.9.0 Bumps [smallrye-common-annotation](https://github.com/smallrye/smallrye-common) from 1.8.0 to 1.9.0. - [Release notes](https://github.com/smallrye/smallrye-common/releases) - [Commits](https://github.com/smallrye/smallrye-common/compare/1.8.0...1.9.0) --- updated-dependencies: - dependency-name: io.smallrye.common:smallrye-common-annotation dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index a1811c7f..4c516458 100644 --- a/pom.xml +++ b/pom.xml @@ -47,7 +47,7 @@ 3.0.4 1.2.2 - 1.8.0 + 1.9.0 2.6.0 0.33.0 4.2.4 From 62b7e207a5d0a11ac582fd656fd38fd4d398021f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 3 Feb 2022 08:44:45 +0000 Subject: [PATCH 10/68] Bump pitest-maven from 1.7.3 to 1.7.4 Bumps [pitest-maven](https://github.com/hcoles/pitest) from 1.7.3 to 1.7.4. - [Release notes](https://github.com/hcoles/pitest/releases) - [Commits](https://github.com/hcoles/pitest/compare/1.7.3...1.7.4) --- updated-dependencies: - dependency-name: org.pitest:pitest-maven dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- implementation/core/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/implementation/core/pom.xml b/implementation/core/pom.xml index aa8c0e77..fb4ae933 100644 --- a/implementation/core/pom.xml +++ b/implementation/core/pom.xml @@ -35,7 +35,7 @@ 2.22.1 0.8.6 - 1.7.3 + 1.7.4 0.15 From 4d0afb3a6d3db4c56d79f42b4fd88b3891daf069 Mon Sep 17 00:00:00 2001 From: Ladislav Thon Date: Wed, 2 Feb 2022 16:39:52 +0100 Subject: [PATCH 11/68] add a Mutiny entrypoint to the programmatic API --- .../faulttolerance/api/FaultTolerance.java | 8 +- .../faulttolerance/api/FaultToleranceSpi.java | 4 +- .../api/FaultToleranceSpiAccess.java | 23 ++-- doc/modules/ROOT/nav.adoc | 2 + .../ROOT/pages/integration/async-types.adoc | 12 ++ doc/modules/ROOT/pages/integration/intro.adoc | 4 +- .../pages/integration/programmatic-api.adoc | 23 ++++ doc/modules/ROOT/pages/usage/extra.adoc | 6 +- .../ROOT/pages/usage/programmatic-api.adoc | 52 +++++--- .../core/async/types}/AsyncTypes.java | 5 +- .../faulttolerance/CdiFaultTolerance.java | 32 ++++- .../faulttolerance/CdiFaultToleranceSpi.java | 8 +- .../FaultToleranceInterceptor.java | 1 + .../faulttolerance/SpecCompatibility.java | 1 + .../config/AsyncValidation.java | 2 +- implementation/mutiny/pom.xml | 37 ++++++ .../mutiny/api/MutinyFaultTolerance.java | 59 +++++++++ .../mutiny/test/MutinyBulkheadTest.java | 45 +++++++ .../mutiny/test/MutinyCircuitBreakerTest.java | 90 +++++++++++++ .../mutiny/test/MutinyFallbackTest.java | 69 ++++++++++ .../mutiny/test/MutinyPassthroughTest.java | 120 ++++++++++++++++++ .../mutiny/test/MutinyResubscriptionTest.java | 50 ++++++++ .../mutiny/test/MutinyRetryTest.java | 71 +++++++++++ .../mutiny/test/MutinyTimeoutTest.java | 63 +++++++++ implementation/standalone/pom.xml | 5 - .../standalone/StandaloneFaultTolerance.java | 32 ++++- .../StandaloneFaultToleranceSpi.java | 8 +- 27 files changed, 767 insertions(+), 65 deletions(-) create mode 100644 doc/modules/ROOT/pages/integration/async-types.adoc create mode 100644 doc/modules/ROOT/pages/integration/programmatic-api.adoc rename implementation/{fault-tolerance/src/main/java/io/smallrye/faulttolerance => core/src/main/java/io/smallrye/faulttolerance/core/async/types}/AsyncTypes.java (90%) create mode 100644 implementation/mutiny/src/main/java/io/smallrye/faulttolerance/mutiny/api/MutinyFaultTolerance.java create mode 100644 implementation/mutiny/src/test/java/io/smallrye/faulttolerance/mutiny/test/MutinyBulkheadTest.java create mode 100644 implementation/mutiny/src/test/java/io/smallrye/faulttolerance/mutiny/test/MutinyCircuitBreakerTest.java create mode 100644 implementation/mutiny/src/test/java/io/smallrye/faulttolerance/mutiny/test/MutinyFallbackTest.java create mode 100644 implementation/mutiny/src/test/java/io/smallrye/faulttolerance/mutiny/test/MutinyPassthroughTest.java create mode 100644 implementation/mutiny/src/test/java/io/smallrye/faulttolerance/mutiny/test/MutinyResubscriptionTest.java create mode 100644 implementation/mutiny/src/test/java/io/smallrye/faulttolerance/mutiny/test/MutinyRetryTest.java create mode 100644 implementation/mutiny/src/test/java/io/smallrye/faulttolerance/mutiny/test/MutinyTimeoutTest.java 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 ab55dd0a..79bde20f 100644 --- a/api/src/main/java/io/smallrye/faulttolerance/api/FaultTolerance.java +++ b/api/src/main/java/io/smallrye/faulttolerance/api/FaultTolerance.java @@ -110,7 +110,7 @@ static Builder> create() { */ static Builder, Callable>> createAsyncCallable( Callable> action) { - return FaultToleranceSpiAccess.createAsync(ft -> ft.adaptCallable(action)); + return FaultToleranceSpiAccess.createAsync(CompletionStage.class, ft -> ft.adaptCallable(action)); } /** @@ -119,7 +119,7 @@ static Builder, Callable>> createAsync */ static Builder, Supplier>> createAsyncSupplier( Supplier> action) { - return FaultToleranceSpiAccess.createAsync(ft -> ft.adaptSupplier(action)); + return FaultToleranceSpiAccess.createAsync(CompletionStage.class, ft -> ft.adaptSupplier(action)); } /** @@ -127,7 +127,7 @@ static Builder, Supplier>> createAsync * The {@code action} is asynchronous and may be offloaded to another thread. */ static Builder, Runnable> createAsyncRunnable(Runnable action) { - return FaultToleranceSpiAccess.createAsync(ft -> ft.adaptRunnable(action)); + return FaultToleranceSpiAccess.createAsync(CompletionStage.class, ft -> ft.adaptRunnable(action)); } /** @@ -139,7 +139,7 @@ static Builder, Runnable> createAsyncRunnable(Runnable act * {@code FaultTolerance.<String>createAsync()}. */ static Builder, FaultTolerance>> createAsync() { - return FaultToleranceSpiAccess.createAsync(Function.identity()); + return FaultToleranceSpiAccess.createAsync(CompletionStage.class, Function.identity()); } /** diff --git a/api/src/main/java/io/smallrye/faulttolerance/api/FaultToleranceSpi.java b/api/src/main/java/io/smallrye/faulttolerance/api/FaultToleranceSpi.java index e7cb6e13..5b4f3b4a 100644 --- a/api/src/main/java/io/smallrye/faulttolerance/api/FaultToleranceSpi.java +++ b/api/src/main/java/io/smallrye/faulttolerance/api/FaultToleranceSpi.java @@ -1,6 +1,5 @@ package io.smallrye.faulttolerance.api; -import java.util.concurrent.CompletionStage; import java.util.function.Function; import io.smallrye.common.annotation.Experimental; @@ -17,8 +16,7 @@ public interface FaultToleranceSpi { FaultTolerance.Builder newBuilder(Function, R> finisher); - FaultTolerance.Builder, R> newAsyncBuilder( - Function>, R> finisher); + 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/FaultToleranceSpiAccess.java index 0b5de9b8..fe6286b2 100644 --- a/api/src/main/java/io/smallrye/faulttolerance/api/FaultToleranceSpiAccess.java +++ b/api/src/main/java/io/smallrye/faulttolerance/api/FaultToleranceSpiAccess.java @@ -1,21 +1,26 @@ package io.smallrye.faulttolerance.api; import java.util.ServiceLoader; -import java.util.concurrent.CompletionStage; import java.util.function.Function; -class FaultToleranceSpiAccess { - static class Holder { - static final FaultToleranceSpi INSTANCE = instantiateSpi(); +import io.smallrye.common.annotation.Experimental; + +/** + * This is an internal API. It may change incompatibly without notice. + * It should not be used outside SmallRye Fault Tolerance. + */ +@Experimental("first attempt at providing programmatic API") +public final class FaultToleranceSpiAccess { + private static class Holder { + private static final FaultToleranceSpi INSTANCE = instantiateSpi(); } - static FaultTolerance.Builder create(Function, R> finisher) { + public static FaultTolerance.Builder create(Function, R> finisher) { return Holder.INSTANCE.newBuilder(finisher); - }; + } - static FaultTolerance.Builder, R> createAsync( - Function>, R> finisher) { - return Holder.INSTANCE.newAsyncBuilder(finisher); + public static FaultTolerance.Builder createAsync(Class asyncType, Function, R> finisher) { + return Holder.INSTANCE.newAsyncBuilder(asyncType, finisher); } static CircuitBreakerMaintenance circuitBreakerMaintenance() { diff --git a/doc/modules/ROOT/nav.adoc b/doc/modules/ROOT/nav.adoc index 609e6f31..ed0df699 100644 --- a/doc/modules/ROOT/nav.adoc +++ b/doc/modules/ROOT/nav.adoc @@ -9,6 +9,8 @@ ** xref:integration/context-propagation.adoc[Context Propagation] ** xref:integration/opentracing.adoc[OpenTracing] ** xref:integration/event-loop.adoc[Event Loop] +** xref:integration/async-types.adoc[Additional Asynchronous Types Integration Concerns] +** xref:integration/programmatic-api.adoc[Programmatic API Integration Concerns] * Internals ** xref:internals/project-structure.adoc[Project Structure] ** xref:internals/instructions.adoc[Instructions] diff --git a/doc/modules/ROOT/pages/integration/async-types.adoc b/doc/modules/ROOT/pages/integration/async-types.adoc new file mode 100644 index 00000000..c4f7335e --- /dev/null +++ b/doc/modules/ROOT/pages/integration/async-types.adoc @@ -0,0 +1,12 @@ += Additional Asynchronous Types Integration Concerns + +This page describes integration concerns for xref:usage/extra.adoc#async-types[Additional Asynchronous Types]. + +To enable support of additional asynchronous types, it is required that the support library is present. +That is: + +* https://smallrye.io/smallrye-mutiny/[Mutiny]: `io.smallrye:smallrye-fault-tolerance-mutiny` +* https://github.com/ReactiveX/RxJava/tree/3.x[RxJava 3]: `io.smallrye:smallrye-fault-tolerance-rxjava3` + +These libraries include some service providers that {smallrye-fault-tolerance} will automatically load and use. +Therefore, no more integration is necessary. diff --git a/doc/modules/ROOT/pages/integration/intro.adoc b/doc/modules/ROOT/pages/integration/intro.adoc index b10b2df4..8683dbd1 100644 --- a/doc/modules/ROOT/pages/integration/intro.adoc +++ b/doc/modules/ROOT/pages/integration/intro.adoc @@ -26,7 +26,9 @@ The remaining integration concerns are described in the following sections: * xref:integration/thread-pool.adoc[providing a thread pool] for `@Asynchronous` methods and other asynchronous tasks; * xref:integration/context-propagation.adoc[enabling Context Propagation] for `@Asynchronous` methods; * xref:integration/opentracing.adoc[enabling OpenTracing integration] with Context Propagation; -* xref:integration/event-loop.adoc[integrating an event loop] for non-blocking method invocations. +* xref:integration/event-loop.adoc[integrating an event loop] for non-blocking method invocations; +* xref:integration/async-types.adoc[integrating additional types] for asynchronous invocations; +* xref:integration/programmatic-api.adoc[integrating the programmatic API] that {smallrye-fault-tolerance} provides. == Standalone Implementation of the Programmatic API diff --git a/doc/modules/ROOT/pages/integration/programmatic-api.adoc b/doc/modules/ROOT/pages/integration/programmatic-api.adoc new file mode 100644 index 00000000..83826017 --- /dev/null +++ b/doc/modules/ROOT/pages/integration/programmatic-api.adoc @@ -0,0 +1,23 @@ += Programmatic API Integration Concerns + +This page describes integration concerns of the xref:usage/programmatic-api.adoc[Programmatic API] of {smallrye-fault-tolerance}. + +== Standalone Implementation + +The standalone implementation provides no integration points. +It is, as the name suggests, completely standalone. + +At the moment, it is not possible to change the thread pool to which actions are offloaded. +A single cached thread pool, obtained using `Executors.newCachedThreadPool()`, is used for all thread offloads. + +Users of the standalone implementation that also use an event loop based library, such as Vert.x, may integrate the event loop support as described in xref:integration/event-loop.adoc[Event Loop]. + +== CDI implementation + +The CDI implementation will use the thread pool an integrator provides (see xref:integration/thread-pool.adoc[Thread Pool]). +This also extends to the context propagation integration (see xref:integration/context-propagation.adoc[Context Propagation]). + +It will also use the event loop support, if integrator provides one (see xref:integration/event-loop.adoc[Event Loop]). + +In other words, runtimes that use the CDI implementation don't have to do any extra integration work. +The existing integration is enough. diff --git a/doc/modules/ROOT/pages/usage/extra.adoc b/doc/modules/ROOT/pages/usage/extra.adoc index 6db15491..3e7b9d30 100644 --- a/doc/modules/ROOT/pages/usage/extra.adoc +++ b/doc/modules/ROOT/pages/usage/extra.adoc @@ -198,7 +198,9 @@ public class MyService { This shows that whatever works for `CompletionStage` also works for the other async types. The implementation internally converts the async types to a `CompletionStage` and back. -This means that to be able to use any particular asynchronous type, the corresponding converter library must be present. +This means that to be able to use any particular asynchronous type, the corresponding converter must be present. +{smallrye-fault-tolerance} provides support libraries for popular asynchronous types, and these support libraries include the corresponding converters. + It is possible that the runtime you use already provides the correct integration. Otherwise, add a dependency to your application: @@ -207,7 +209,7 @@ Otherwise, add a dependency to your application: .Quarkus **** -In Quarkus, the Mutiny converter library is present by default. +In Quarkus, the Mutiny support library is present by default. You can use fault tolerance on methods that return `Uni` out of the box. **** diff --git a/doc/modules/ROOT/pages/usage/programmatic-api.adoc b/doc/modules/ROOT/pages/usage/programmatic-api.adoc index 8645e2c4..3763e8a3 100644 --- a/doc/modules/ROOT/pages/usage/programmatic-api.adoc +++ b/doc/modules/ROOT/pages/usage/programmatic-api.adoc @@ -254,29 +254,49 @@ Which one do you want to call depends on the type used to represent the action. | `adaptRunnable(Runnable)` -> `Runnable` |=== -== Configuration and Metrics +== Mutiny Support -As mentioned above, with the single exception of `MP_Fault_Tolerance_NonFallback_Enabled`, there is no external configuration support. -This may change in the future, though possibly only in the CDI implementation. +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:usage/extra.adoc#async-types[Additional Asynchronous Types]. -At the moment, the programmatic API of {smallrye-fault-tolerance} is not integrated with metrics. -This will change in the future, though possibly only in the CDI implementation. +This entrypoint is called `MutinyFaultTolerance` and it includes static factory methods for creating a `Callable>`, `Supplier` and `FaultTolerance>`. +Guarding a `Multi` is not supported. -== Integration Concerns +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: -=== Standalone Implementation +[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() + .build(); -The standalone implementation provides no integration points. -It is, as the name suggests, completely standalone. + public Uni hello() { + return guard.get(); + } +} +---- -At the moment, it is not possible to change the thread pool to which actions are offloaded. -A single cached thread pool, obtained using `Executors.newCachedThreadPool()`, is used for all thread offloads. +<1> The call to `externalService.hello()` is supposed to return `Uni`. -Users of the standalone implementation that also use an event loop based library, such as Vert.x, may integrate the event loop support as described in xref:integration/event-loop.adoc[Event Loop]. +Note that the `Uni` type is lazy, so the action itself won't execute until the guarded `Uni` is subscribed to. -=== CDI implementation +.Quarkus +**** +In Quarkus, the Mutiny support library is present by default. +You can use `MutinyFaultTolerance` out of the box. +**** -The CDI implementation will use the thread pool an integrator provides (see xref:integration/thread-pool.adoc[Thread Pool]). -This also extends to the context propagation integration (see xref:integration/context-propagation.adoc[Context Propagation]). +== Configuration and Metrics + +As mentioned above, with the single exception of `MP_Fault_Tolerance_NonFallback_Enabled`, there is no external configuration support. +This may change in the future, though possibly only in the CDI implementation. + +At the moment, the programmatic API of {smallrye-fault-tolerance} is not integrated with metrics. +This will change in the future, though possibly only in the CDI implementation. + +== Integration Concerns -It will also use the event loop support, if integrator provides one (see xref:integration/event-loop.adoc[Event Loop]). +Integration concerns, which are particularly interesting for users of the standalone implementation, are xref:integration/programmatic-api.adoc[described] in the integration section. diff --git a/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/AsyncTypes.java b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/async/types/AsyncTypes.java similarity index 90% rename from implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/AsyncTypes.java rename to implementation/core/src/main/java/io/smallrye/faulttolerance/core/async/types/AsyncTypes.java index eee62b8a..039c43b0 100644 --- a/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/AsyncTypes.java +++ b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/async/types/AsyncTypes.java @@ -1,4 +1,4 @@ -package io.smallrye.faulttolerance; +package io.smallrye.faulttolerance.core.async.types; import java.util.Collection; import java.util.Collections; @@ -6,9 +6,6 @@ import java.util.Map; import java.util.ServiceLoader; -import io.smallrye.faulttolerance.core.async.types.AsyncTypeConverter; - -// TODO reduce statics? public class AsyncTypes { private static final Map, AsyncTypeConverter> registry; diff --git a/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/CdiFaultTolerance.java b/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/CdiFaultTolerance.java index 37c48ea9..08f5b519 100644 --- a/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/CdiFaultTolerance.java +++ b/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/CdiFaultTolerance.java @@ -19,6 +19,8 @@ import io.smallrye.faulttolerance.core.InvocationContext; import io.smallrye.faulttolerance.core.async.CompletionStageExecution; import io.smallrye.faulttolerance.core.async.RememberEventLoop; +import io.smallrye.faulttolerance.core.async.types.AsyncTypes; +import io.smallrye.faulttolerance.core.async.types.AsyncTypesConversion; import io.smallrye.faulttolerance.core.bulkhead.CompletionStageThreadPoolBulkhead; import io.smallrye.faulttolerance.core.bulkhead.SemaphoreBulkhead; import io.smallrye.faulttolerance.core.circuit.breaker.CircuitBreaker; @@ -69,6 +71,7 @@ static class BuilderImpl implements Builder { private final EventLoop eventLoop; private final CircuitBreakerMaintenanceImpl cbMaintenance; private final boolean isAsync; + private final Class asyncType; // ignored when isAsync == false private final Function, R> finisher; private BulkheadBuilderImpl bulkheadBuilder; @@ -79,7 +82,7 @@ static class BuilderImpl implements Builder { private boolean offloadToAnotherThread; BuilderImpl(boolean ftEnabled, Executor executor, Timer timer, EventLoop eventLoop, - CircuitBreakerMaintenanceImpl cbMaintenance, boolean isAsync, + CircuitBreakerMaintenanceImpl cbMaintenance, boolean isAsync, Class asyncType, Function, R> finisher) { this.ftEnabled = ftEnabled; this.executor = executor; @@ -87,6 +90,7 @@ static class BuilderImpl implements Builder { this.eventLoop = eventLoop; this.cbMaintenance = cbMaintenance; this.isAsync = isAsync; + this.asyncType = asyncType; this.finisher = finisher; } @@ -180,8 +184,26 @@ private FaultToleranceStrategy buildSyncStrategy() { return result; } - private FaultToleranceStrategy> buildAsyncStrategy() { - FaultToleranceStrategy> result = invocation(); + private FaultToleranceStrategy buildAsyncStrategy() { + // FaultToleranceStrategy expects that the input type and output type are the same, which + // isn't true for the conversion strategies used below (even though the entire chain does + // retain the type, the conversions are only intermediate) + // that's why we use raw types here, to work around this design choice + + FaultToleranceStrategy result = invocation(); + + result = new AsyncTypesConversion.ToCompletionStage(result, AsyncTypes.get(asyncType)); + + result = buildCompletionStageChain(result); + + result = new AsyncTypesConversion.FromCompletionStage(result, AsyncTypes.get(asyncType)); + + return result; + } + + private FaultToleranceStrategy> buildCompletionStageChain( + FaultToleranceStrategy> invocation) { + FaultToleranceStrategy> result = invocation; // thread offload is always enabled Executor executor = offloadToAnotherThread ? this.executor : DirectExecutor.INSTANCE; @@ -221,8 +243,8 @@ private FaultToleranceStrategy> buildAsyncStrategy() { // fallback is always enabled if (fallbackBuilder != null) { - FallbackFunction> fallbackFunction = ctx -> (CompletionStage) fallbackBuilder.handler - .apply(ctx.failure); + FallbackFunction> fallbackFunction = ctx -> AsyncTypes.toCompletionStageIfRequired( + fallbackBuilder.handler.apply(ctx.failure), asyncType); result = new CompletionStageFallback<>(result, "unknown", fallbackFunction, createExceptionDecision(fallbackBuilder.skipOn, fallbackBuilder.applyOn)); } diff --git a/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/CdiFaultToleranceSpi.java b/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/CdiFaultToleranceSpi.java index f44f98a3..5987901e 100644 --- a/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/CdiFaultToleranceSpi.java +++ b/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/CdiFaultToleranceSpi.java @@ -1,6 +1,5 @@ package io.smallrye.faulttolerance; -import java.util.concurrent.CompletionStage; import java.util.concurrent.ExecutorService; import java.util.function.Function; @@ -66,15 +65,14 @@ public int priority() { public FaultTolerance.Builder newBuilder(Function, R> finisher) { Dependencies deps = getDependencies(); return new CdiFaultTolerance.BuilderImpl<>(deps.ftEnabled, deps.asyncExecutor(), deps.timer(), deps.eventLoop(), - deps.cbMaintenance, false, finisher); + deps.cbMaintenance, false, null, finisher); } @Override - public FaultTolerance.Builder, R> newAsyncBuilder( - Function>, R> finisher) { + public FaultTolerance.Builder newAsyncBuilder(Class asyncType, Function, R> finisher) { Dependencies deps = getDependencies(); return new CdiFaultTolerance.BuilderImpl<>(deps.ftEnabled, deps.asyncExecutor(), deps.timer(), deps.eventLoop(), - deps.cbMaintenance, true, finisher); + deps.cbMaintenance, true, asyncType, finisher); } @Override 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 477f03ac..0b745f4b 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,6 +51,7 @@ import io.smallrye.faulttolerance.core.async.CompletionStageExecution; import io.smallrye.faulttolerance.core.async.FutureExecution; import io.smallrye.faulttolerance.core.async.RememberEventLoop; +import io.smallrye.faulttolerance.core.async.types.AsyncTypes; import io.smallrye.faulttolerance.core.async.types.AsyncTypesConversion; import io.smallrye.faulttolerance.core.bulkhead.CompletionStageThreadPoolBulkhead; import io.smallrye.faulttolerance.core.bulkhead.FutureThreadPoolBulkhead; diff --git a/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/SpecCompatibility.java b/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/SpecCompatibility.java index fd4ce26e..91a9bd6a 100644 --- a/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/SpecCompatibility.java +++ b/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/SpecCompatibility.java @@ -8,6 +8,7 @@ import org.eclipse.microprofile.config.inject.ConfigProperty; import io.smallrye.faulttolerance.config.FaultToleranceOperation; +import io.smallrye.faulttolerance.core.async.types.AsyncTypes; @Singleton public class SpecCompatibility { diff --git a/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/config/AsyncValidation.java b/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/config/AsyncValidation.java index 7999b2a3..588f6939 100644 --- a/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/config/AsyncValidation.java +++ b/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/config/AsyncValidation.java @@ -3,8 +3,8 @@ import java.util.StringJoiner; import java.util.concurrent.Future; -import io.smallrye.faulttolerance.AsyncTypes; import io.smallrye.faulttolerance.core.async.types.AsyncTypeConverter; +import io.smallrye.faulttolerance.core.async.types.AsyncTypes; final class AsyncValidation { static boolean isAcceptableReturnType(Class returnType) { diff --git a/implementation/mutiny/pom.xml b/implementation/mutiny/pom.xml index 79044683..56fce4f6 100644 --- a/implementation/mutiny/pom.xml +++ b/implementation/mutiny/pom.xml @@ -13,6 +13,20 @@ SmallRye Fault Tolerance: Mutiny Integration + + io.smallrye + smallrye-fault-tolerance-api + + + jakarta.enterprise + jakarta.enterprise.cdi-api + + + org.eclipse.microprofile.fault-tolerance + microprofile-fault-tolerance-api + + + io.smallrye smallrye-fault-tolerance-core @@ -22,5 +36,28 @@ io.smallrye.reactive mutiny + + + io.smallrye + smallrye-fault-tolerance-standalone + test + + + io.smallrye + smallrye-fault-tolerance-core + test-jar + test + + + + org.junit.jupiter + junit-jupiter + test + + + org.assertj + assertj-core + test + 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 new file mode 100644 index 00000000..af386d50 --- /dev/null +++ b/implementation/mutiny/src/main/java/io/smallrye/faulttolerance/mutiny/api/MutinyFaultTolerance.java @@ -0,0 +1,59 @@ +package io.smallrye.faulttolerance.mutiny.api; + +import java.util.concurrent.Callable; +import java.util.function.Function; +import java.util.function.Supplier; + +import io.smallrye.faulttolerance.api.FaultTolerance; +import io.smallrye.faulttolerance.api.FaultToleranceSpiAccess; +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. + */ +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. + */ + static FaultTolerance.Builder, Callable>> createCallable(Callable> action) { + return FaultToleranceSpiAccess.createAsync(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. + */ + static FaultTolerance.Builder, Supplier>> createSupplier(Supplier> action) { + return FaultToleranceSpiAccess.createAsync(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()}. + */ + static FaultTolerance.Builder, FaultTolerance>> create() { + return FaultToleranceSpiAccess.createAsync(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 new file mode 100644 index 00000000..b034b96e --- /dev/null +++ b/implementation/mutiny/src/test/java/io/smallrye/faulttolerance/mutiny/test/MutinyBulkheadTest.java @@ -0,0 +1,45 @@ +package io.smallrye.faulttolerance.mutiny.test; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.concurrent.TimeUnit; + +import org.eclipse.microprofile.faulttolerance.exceptions.BulkheadException; +import org.junit.jupiter.api.Test; + +import io.smallrye.faulttolerance.api.FaultTolerance; +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() + .withBulkhead().limit(5).queueSize(5).done() + .withFallback().handler(this::fallback).applyOn(BulkheadException.class).done() + .withThreadOffload(true) + .build(); + + Party party = Party.create(5); + + for (int i = 0; i < 10; i++) { + guarded.call(() -> { + party.participant().attend(); + return Uni.createFrom().item("ignored"); + }).subscribeAsCompletionStage(); + } + + party.organizer().waitForAll(); + + assertThat(guarded.call(() -> Uni.createFrom().item("value")).subscribeAsCompletionStage()) + .succeedsWithin(10, TimeUnit.SECONDS) + .isEqualTo("fallback"); + + party.organizer().disband(); + } + + public Uni fallback() { + return Uni.createFrom().item("fallback"); + } +} 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 new file mode 100644 index 00000000..d895868a --- /dev/null +++ b/implementation/mutiny/src/test/java/io/smallrye/faulttolerance/mutiny/test/MutinyCircuitBreakerTest.java @@ -0,0 +1,90 @@ +package io.smallrye.faulttolerance.mutiny.test; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.function.Supplier; + +import org.eclipse.microprofile.faulttolerance.exceptions.CircuitBreakerOpenException; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import io.smallrye.faulttolerance.api.FaultTolerance; +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(); + } + + @Test + public void circuitBreaker() { + Supplier> guarded = MutinyFaultTolerance.createSupplier(this::action) + .withCircuitBreaker().requestVolumeThreshold(4).done() + .withFallback().handler(this::fallback).applyOn(CircuitBreakerOpenException.class).done() + .build(); + + for (int i = 0; i < 4; i++) { + assertThat(guarded.get().subscribeAsCompletionStage()) + .failsWithin(10, TimeUnit.SECONDS) + .withThrowableOfType(ExecutionException.class) // caused by AssertJ calling future.get() + .withCauseExactlyInstanceOf(TestException.class); + } + + assertThat(guarded.get().subscribeAsCompletionStage()) + .succeedsWithin(10, TimeUnit.SECONDS) + .isEqualTo("fallback"); + } + + @Test + public void circuitBreakerWithSkipOn() { + Supplier> guarded = MutinyFaultTolerance.createSupplier(this::action) + .withCircuitBreaker().requestVolumeThreshold(4).skipOn(TestException.class).done() + .withFallback().handler(this::fallback).applyOn(CircuitBreakerOpenException.class).done() + .build(); + + for (int i = 0; i < 4; i++) { + assertThat(guarded.get().subscribeAsCompletionStage()) + .failsWithin(10, TimeUnit.SECONDS) + .withThrowableOfType(ExecutionException.class) // caused by AssertJ calling future.get() + .withCauseExactlyInstanceOf(TestException.class); + } + + assertThat(guarded.get().subscribeAsCompletionStage()) + .failsWithin(10, TimeUnit.SECONDS) + .withThrowableOfType(ExecutionException.class) // caused by AssertJ calling future.get() + .withCauseExactlyInstanceOf(TestException.class); + } + + @Test + public void circuitBreakerWithFailOn() { + Supplier> guarded = MutinyFaultTolerance.createSupplier(this::action) + .withCircuitBreaker().requestVolumeThreshold(4).failOn(RuntimeException.class).done() + .withFallback().handler(this::fallback).applyOn(CircuitBreakerOpenException.class).done() + .build(); + + for (int i = 0; i < 4; i++) { + assertThat(guarded.get().subscribeAsCompletionStage()) + .failsWithin(10, TimeUnit.SECONDS) + .withThrowableOfType(ExecutionException.class) // caused by AssertJ calling future.get() + .withCauseExactlyInstanceOf(TestException.class); + } + + assertThat(guarded.get().subscribeAsCompletionStage()) + .failsWithin(10, TimeUnit.SECONDS) + .withThrowableOfType(ExecutionException.class) // caused by AssertJ calling future.get() + .withCauseExactlyInstanceOf(TestException.class); + } + + public Uni action() { + return Uni.createFrom().failure(new TestException()); + } + + public Uni fallback() { + return Uni.createFrom().item("fallback"); + } +} 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 new file mode 100644 index 00000000..5baa2883 --- /dev/null +++ b/implementation/mutiny/src/test/java/io/smallrye/faulttolerance/mutiny/test/MutinyFallbackTest.java @@ -0,0 +1,69 @@ +package io.smallrye.faulttolerance.mutiny.test; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.function.Supplier; + +import org.junit.jupiter.api.Test; + +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) + .withFallback().handler(this::fallback).done() + .build(); + + assertThat(guarded.get().subscribeAsCompletionStage()) + .succeedsWithin(10, TimeUnit.SECONDS) + .isEqualTo("fallback"); + } + + @Test + public void fallbackWithFunction() { + Supplier> guarded = MutinyFaultTolerance.createSupplier(this::action) + .withFallback().handler(e -> Uni.createFrom().item(e.getClass().getSimpleName())).done() + .build(); + + assertThat(guarded.get().subscribeAsCompletionStage()) + .succeedsWithin(10, TimeUnit.SECONDS) + .isEqualTo("TestException"); + } + + @Test + public void fallbackWithSkipOn() { + Supplier> guarded = MutinyFaultTolerance.createSupplier(this::action) + .withFallback().handler(this::fallback).skipOn(TestException.class).done() + .build(); + + assertThat(guarded.get().subscribeAsCompletionStage()) + .failsWithin(10, TimeUnit.SECONDS) + .withThrowableOfType(ExecutionException.class) // caused by AssertJ calling future.get() + .withCauseExactlyInstanceOf(TestException.class); + } + + @Test + public void fallbackWithApplyOn() { + Supplier> guarded = MutinyFaultTolerance.createSupplier(this::action) + .withFallback().handler(this::fallback).applyOn(RuntimeException.class).done() + .build(); + + assertThat(guarded.get().subscribeAsCompletionStage()) + .failsWithin(10, TimeUnit.SECONDS) + .withThrowableOfType(ExecutionException.class) // caused by AssertJ calling future.get() + .withCauseExactlyInstanceOf(TestException.class); + } + + public Uni action() { + return Uni.createFrom().failure(new TestException()); + } + + public Uni fallback() { + return Uni.createFrom().item("fallback"); + } +} 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 new file mode 100644 index 00000000..1e58e251 --- /dev/null +++ b/implementation/mutiny/src/test/java/io/smallrye/faulttolerance/mutiny/test/MutinyPassthroughTest.java @@ -0,0 +1,120 @@ +package io.smallrye.faulttolerance.mutiny.test; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.function.Supplier; + +import org.junit.jupiter.api.Test; + +import io.smallrye.faulttolerance.api.FaultTolerance; +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(); + + assertThat(guard.call(this::completeSuccessfully).subscribeAsCompletionStage()) + .succeedsWithin(10, TimeUnit.SECONDS) + .isEqualTo("value"); + } + + @Test + public void passthroughCompletedExceptionally() throws Exception { + FaultTolerance> guard = MutinyFaultTolerance. create().build(); + + assertThat(guard.call(this::completeExceptionally).subscribeAsCompletionStage()) + .failsWithin(10, TimeUnit.SECONDS) + .withThrowableOfType(ExecutionException.class) // caused by AssertJ calling future.get() + .withCauseExactlyInstanceOf(TestException.class); + } + + @Test + public void passthroughThrownException() throws Exception { + FaultTolerance> guard = MutinyFaultTolerance. create().build(); + + assertThat(guard.call(this::throwException).subscribeAsCompletionStage()) + .failsWithin(10, TimeUnit.SECONDS) + .withThrowableOfType(ExecutionException.class) // caused by AssertJ calling future.get() + .withCauseExactlyInstanceOf(TestException.class); + } + + @Test + public void callablePassthroughCompletedSuccessfully() throws Exception { + Callable> guard = MutinyFaultTolerance.createCallable(this::completeSuccessfully).build(); + + assertThat(guard.call().subscribeAsCompletionStage()) + .succeedsWithin(10, TimeUnit.SECONDS) + .isEqualTo("value"); + } + + @Test + public void callablePassthroughCompletedExceptionally() throws Exception { + Callable> guard = MutinyFaultTolerance.createCallable(this::completeExceptionally).build(); + + assertThat(guard.call().subscribeAsCompletionStage()) + .failsWithin(10, TimeUnit.SECONDS) + .withThrowableOfType(ExecutionException.class) // caused by AssertJ calling future.get() + .withCauseExactlyInstanceOf(TestException.class); + } + + @Test + public void callablePassthroughThrownException() throws Exception { + Callable> guard = MutinyFaultTolerance.createCallable(this::throwException).build(); + + assertThat(guard.call().subscribeAsCompletionStage()) + .failsWithin(10, TimeUnit.SECONDS) + .withThrowableOfType(ExecutionException.class) // caused by AssertJ calling future.get() + .withCauseExactlyInstanceOf(TestException.class); + } + + @Test + public void supplierPassthroughCompletedSuccessfully() { + Supplier> guard = MutinyFaultTolerance.createSupplier(this::completeSuccessfully).build(); + + assertThat(guard.get().subscribeAsCompletionStage()) + .succeedsWithin(10, TimeUnit.SECONDS) + .isEqualTo("value"); + } + + @Test + public void supplierPassthroughCompletedExceptionally() { + Supplier> guard = MutinyFaultTolerance.createSupplier(this::completeExceptionally).build(); + + assertThat(guard.get().subscribeAsCompletionStage()) + .failsWithin(10, TimeUnit.SECONDS) + .withThrowableOfType(ExecutionException.class) // caused by AssertJ calling future.get() + .withCauseExactlyInstanceOf(TestException.class); + } + + @Test + public void supplierPassthroughThrownException() { + Supplier> guard = MutinyFaultTolerance.createSupplier(this::throwRuntimeException).build(); + + assertThat(guard.get().subscribeAsCompletionStage()) + .failsWithin(10, TimeUnit.SECONDS) + .withThrowableOfType(ExecutionException.class) // caused by AssertJ calling future.get() + .withCauseExactlyInstanceOf(RuntimeException.class); + } + + private Uni completeSuccessfully() { + return Uni.createFrom().item("value"); + } + + private Uni completeExceptionally() { + return Uni.createFrom().failure(new TestException()); + } + + private Uni throwException() throws TestException { + throw new TestException(); + } + + private Uni throwRuntimeException() { + throw new RuntimeException(new TestException()); + } +} 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 new file mode 100644 index 00000000..36a10555 --- /dev/null +++ b/implementation/mutiny/src/test/java/io/smallrye/faulttolerance/mutiny/test/MutinyResubscriptionTest.java @@ -0,0 +1,50 @@ +package io.smallrye.faulttolerance.mutiny.test; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Supplier; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import io.smallrye.faulttolerance.core.util.TestException; +import io.smallrye.faulttolerance.mutiny.api.MutinyFaultTolerance; +import io.smallrye.mutiny.Uni; + +public class MutinyResubscriptionTest { + private final AtomicInteger counter = new AtomicInteger(); + + @BeforeEach + public void setUp() { + counter.set(0); + } + + @Test + public void doubleRetry() { + // this test verifies resubscription, which is triggered via retry + + Supplier> guarded = MutinyFaultTolerance.createSupplier(this::action) + .withRetry().maxRetries(3).done() + .build(); + + Uni hello = guarded.get() + .onFailure().retry().atMost(2) + .onFailure().recoverWithItem("hello"); + + assertThat(hello.subscribeAsCompletionStage()) + .succeedsWithin(10, TimeUnit.SECONDS) + .isEqualTo("hello"); + + // the MutinyFaultTolerance guard has maxRetries of 3, so 1 initial attempt + 3 retries = 4 total + // the onFailure().retry() handler does 1 initial attempt + 2 retries = 3 total + assertThat(counter).hasValue(4 * 3); + + } + + public Uni action() { + counter.incrementAndGet(); + return Uni.createFrom().failure(new TestException()); + } +} 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 new file mode 100644 index 00000000..116cdaf1 --- /dev/null +++ b/implementation/mutiny/src/test/java/io/smallrye/faulttolerance/mutiny/test/MutinyRetryTest.java @@ -0,0 +1,71 @@ +package io.smallrye.faulttolerance.mutiny.test; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Supplier; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import io.smallrye.faulttolerance.core.util.TestException; +import io.smallrye.faulttolerance.mutiny.api.MutinyFaultTolerance; +import io.smallrye.mutiny.Uni; + +public class MutinyRetryTest { + private final AtomicInteger counter = new AtomicInteger(); + + @BeforeEach + public void setUp() { + counter.set(0); + } + + @Test + public void retry() { + Supplier> guarded = MutinyFaultTolerance.createSupplier(this::action) + .withRetry().maxRetries(3).done() + .withFallback().handler(this::fallback).applyOn(TestException.class).done() + .build(); + + assertThat(guarded.get().subscribeAsCompletionStage()) + .succeedsWithin(10, TimeUnit.SECONDS) + .isEqualTo("fallback"); + assertThat(counter).hasValue(4); // 1 initial invocation + 3 retries + } + + @Test + public void retryWithAbortOn() { + Supplier> guarded = MutinyFaultTolerance.createSupplier(this::action) + .withRetry().maxRetries(3).abortOn(TestException.class).done() + .withFallback().handler(this::fallback).applyOn(TestException.class).done() + .build(); + + assertThat(guarded.get().subscribeAsCompletionStage()) + .succeedsWithin(10, TimeUnit.SECONDS) + .isEqualTo("fallback"); + assertThat(counter).hasValue(1); // 1 initial invocation + } + + @Test + public void retryWithRetryOn() { + Supplier> guarded = MutinyFaultTolerance.createSupplier(this::action) + .withRetry().maxRetries(3).retryOn(RuntimeException.class).done() + .withFallback().handler(this::fallback).applyOn(TestException.class).done() + .build(); + + assertThat(guarded.get().subscribeAsCompletionStage()) + .succeedsWithin(10, TimeUnit.SECONDS) + .isEqualTo("fallback"); + assertThat(counter).hasValue(1); // 1 initial invocation + } + + public Uni action() { + counter.incrementAndGet(); + return Uni.createFrom().failure(new TestException()); + } + + public Uni fallback() { + return Uni.createFrom().item("fallback"); + } +} 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 new file mode 100644 index 00000000..5972c18d --- /dev/null +++ b/implementation/mutiny/src/test/java/io/smallrye/faulttolerance/mutiny/test/MutinyTimeoutTest.java @@ -0,0 +1,63 @@ +package io.smallrye.faulttolerance.mutiny.test; + +import static io.smallrye.faulttolerance.core.util.Timing.timed; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.withinPercentage; + +import java.time.Duration; +import java.time.temporal.ChronoUnit; +import java.util.concurrent.Callable; +import java.util.concurrent.TimeUnit; + +import org.eclipse.microprofile.faulttolerance.exceptions.TimeoutException; +import org.junit.jupiter.api.Test; + +import io.smallrye.faulttolerance.mutiny.api.MutinyFaultTolerance; +import io.smallrye.mutiny.Uni; + +public class MutinyTimeoutTest { + @Test + public void nonblockingAsyncTimeout() throws Exception { + Callable> guarded = MutinyFaultTolerance.createCallable(this::nonblockingAction) + .withTimeout().duration(1, ChronoUnit.SECONDS).done() + .withFallback().applyOn(TimeoutException.class).handler(this::fallback).done() + .build(); + + long time = timed(() -> { + assertThat(guarded.call().subscribeAsCompletionStage()) + .succeedsWithin(5, TimeUnit.SECONDS) + .isEqualTo("fallback"); + }); + assertThat(time).isCloseTo(1000, withinPercentage(20)); + } + + @Test + public void blockingAsyncTimeout() throws Exception { + Callable> guarded = MutinyFaultTolerance.createCallable(this::blockingAction) + .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(); + + long time = timed(() -> { + assertThat(guarded.call().subscribeAsCompletionStage()) + .succeedsWithin(5, TimeUnit.SECONDS) + .isEqualTo("fallback"); + }); + assertThat(time).isCloseTo(1000, withinPercentage(20)); + } + + public Uni nonblockingAction() { + return Uni.createFrom().item("value") + .onItem().delayIt().by(Duration.ofSeconds(10)); + } + + public Uni blockingAction() throws InterruptedException { + Thread.sleep(10_000); + return Uni.createFrom().item("value"); + } + + public Uni fallback() { + return Uni.createFrom().item("fallback"); + } +} diff --git a/implementation/standalone/pom.xml b/implementation/standalone/pom.xml index 03840dbc..0c35739a 100644 --- a/implementation/standalone/pom.xml +++ b/implementation/standalone/pom.xml @@ -44,11 +44,6 @@ junit-jupiter test - - org.junit-pioneer - junit-pioneer - test - org.assertj assertj-core diff --git a/implementation/standalone/src/main/java/io/smallrye/faulttolerance/standalone/StandaloneFaultTolerance.java b/implementation/standalone/src/main/java/io/smallrye/faulttolerance/standalone/StandaloneFaultTolerance.java index 8f935fe2..3dd9135b 100644 --- a/implementation/standalone/src/main/java/io/smallrye/faulttolerance/standalone/StandaloneFaultTolerance.java +++ b/implementation/standalone/src/main/java/io/smallrye/faulttolerance/standalone/StandaloneFaultTolerance.java @@ -19,6 +19,8 @@ import io.smallrye.faulttolerance.core.InvocationContext; import io.smallrye.faulttolerance.core.async.CompletionStageExecution; import io.smallrye.faulttolerance.core.async.RememberEventLoop; +import io.smallrye.faulttolerance.core.async.types.AsyncTypes; +import io.smallrye.faulttolerance.core.async.types.AsyncTypesConversion; import io.smallrye.faulttolerance.core.bulkhead.CompletionStageThreadPoolBulkhead; import io.smallrye.faulttolerance.core.bulkhead.SemaphoreBulkhead; import io.smallrye.faulttolerance.core.circuit.breaker.CircuitBreaker; @@ -69,6 +71,7 @@ static class BuilderImpl implements Builder { private final EventLoop eventLoop; private final StandaloneCircuitBreakerMaintenance cbMaintenance; private final boolean isAsync; + private final Class asyncType; // ignored when isAsync == false private final Function, R> finisher; private BulkheadBuilderImpl bulkheadBuilder; @@ -79,7 +82,7 @@ static class BuilderImpl implements Builder { private boolean offloadToAnotherThread; BuilderImpl(boolean ftEnabled, Executor executor, Timer timer, EventLoop eventLoop, - StandaloneCircuitBreakerMaintenance cbMaintenance, boolean isAsync, + StandaloneCircuitBreakerMaintenance cbMaintenance, boolean isAsync, Class asyncType, Function, R> finisher) { this.ftEnabled = ftEnabled; this.executor = executor; @@ -87,6 +90,7 @@ static class BuilderImpl implements Builder { this.eventLoop = eventLoop; this.cbMaintenance = cbMaintenance; this.isAsync = isAsync; + this.asyncType = asyncType; this.finisher = finisher; } @@ -180,8 +184,26 @@ private FaultToleranceStrategy buildSyncStrategy() { return result; } - private FaultToleranceStrategy> buildAsyncStrategy() { - FaultToleranceStrategy> result = invocation(); + private FaultToleranceStrategy buildAsyncStrategy() { + // FaultToleranceStrategy expects that the input type and output type are the same, which + // isn't true for the conversion strategies used below (even though the entire chain does + // retain the type, the conversions are only intermediate) + // that's why we use raw types here, to work around this design choice + + FaultToleranceStrategy result = invocation(); + + result = new AsyncTypesConversion.ToCompletionStage(result, AsyncTypes.get(asyncType)); + + result = buildCompletionStageChain(result); + + result = new AsyncTypesConversion.FromCompletionStage(result, AsyncTypes.get(asyncType)); + + return result; + } + + private FaultToleranceStrategy> buildCompletionStageChain( + FaultToleranceStrategy> invocation) { + FaultToleranceStrategy> result = invocation; // thread offload is always enabled Executor executor = offloadToAnotherThread ? this.executor : DirectExecutor.INSTANCE; @@ -221,8 +243,8 @@ private FaultToleranceStrategy> buildAsyncStrategy() { // fallback is always enabled if (fallbackBuilder != null) { - FallbackFunction> fallbackFunction = ctx -> (CompletionStage) fallbackBuilder.handler - .apply(ctx.failure); + FallbackFunction> fallbackFunction = ctx -> AsyncTypes.toCompletionStageIfRequired( + fallbackBuilder.handler.apply(ctx.failure), asyncType); result = new CompletionStageFallback<>(result, "unknown", fallbackFunction, createExceptionDecision(fallbackBuilder.skipOn, fallbackBuilder.applyOn)); } diff --git a/implementation/standalone/src/main/java/io/smallrye/faulttolerance/standalone/StandaloneFaultToleranceSpi.java b/implementation/standalone/src/main/java/io/smallrye/faulttolerance/standalone/StandaloneFaultToleranceSpi.java index 7fb74842..b6676f19 100644 --- a/implementation/standalone/src/main/java/io/smallrye/faulttolerance/standalone/StandaloneFaultToleranceSpi.java +++ b/implementation/standalone/src/main/java/io/smallrye/faulttolerance/standalone/StandaloneFaultToleranceSpi.java @@ -1,6 +1,5 @@ package io.smallrye.faulttolerance.standalone; -import java.util.concurrent.CompletionStage; import java.util.concurrent.Executor; import java.util.concurrent.Executors; import java.util.function.Function; @@ -41,15 +40,14 @@ public int priority() { public FaultTolerance.Builder newBuilder(Function, R> finisher) { Dependencies deps = DependenciesHolder.INSTANCE; return new StandaloneFaultTolerance.BuilderImpl<>(deps.ftEnabled, deps.executor, deps.timer, deps.eventLoop, - deps.cbMaintenance, false, finisher); + deps.cbMaintenance, false, null, finisher); } @Override - public FaultTolerance.Builder, R> newAsyncBuilder( - Function>, R> finisher) { + public FaultTolerance.Builder newAsyncBuilder(Class asyncType, Function, R> finisher) { Dependencies deps = DependenciesHolder.INSTANCE; return new StandaloneFaultTolerance.BuilderImpl<>(deps.ftEnabled, deps.executor, deps.timer, deps.eventLoop, - deps.cbMaintenance, true, finisher); + deps.cbMaintenance, true, asyncType, finisher); } @Override From 70e77aa9a2a37842222ba41fed57ab49d32ff12a Mon Sep 17 00:00:00 2001 From: Ladislav Thon Date: Thu, 3 Feb 2022 10:38:45 +0100 Subject: [PATCH 12/68] streamline the FaultToleranceSpiAccess usage --- .../faulttolerance/api/FaultTolerance.java | 18 +++++++++--------- .../api/FaultToleranceSpiAccess.java | 12 ++---------- .../mutiny/api/MutinyFaultTolerance.java | 6 +++--- 3 files changed, 14 insertions(+), 22 deletions(-) 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 79bde20f..d60c16ab 100644 --- a/api/src/main/java/io/smallrye/faulttolerance/api/FaultTolerance.java +++ b/api/src/main/java/io/smallrye/faulttolerance/api/FaultTolerance.java @@ -65,7 +65,7 @@ public interface FaultTolerance { * circuit breakers. */ static CircuitBreakerMaintenance circuitBreakerMaintenance() { - return FaultToleranceSpiAccess.circuitBreakerMaintenance(); + return FaultToleranceSpiAccess.get().circuitBreakerMaintenance(); } /** @@ -73,7 +73,7 @@ static CircuitBreakerMaintenance circuitBreakerMaintenance() { * The {@code action} is synchronous and is always executed on the original thread. */ static Builder> createCallable(Callable action) { - return FaultToleranceSpiAccess.create(ft -> ft.adaptCallable(action)); + return FaultToleranceSpiAccess.get().newBuilder(ft -> ft.adaptCallable(action)); } /** @@ -81,7 +81,7 @@ static Builder> createCallable(Callable action) { * The {@code action} is synchronous and is always executed on the original thread. */ static Builder> createSupplier(Supplier action) { - return FaultToleranceSpiAccess.create(ft -> ft.adaptSupplier(action)); + return FaultToleranceSpiAccess.get().newBuilder(ft -> ft.adaptSupplier(action)); } /** @@ -89,7 +89,7 @@ static Builder> createSupplier(Supplier action) { * The {@code action} is synchronous and is always executed on the original thread. */ static Builder createRunnable(Runnable action) { - return FaultToleranceSpiAccess.create(ft -> ft.adaptRunnable(action)); + return FaultToleranceSpiAccess.get().newBuilder(ft -> ft.adaptRunnable(action)); } /** @@ -101,7 +101,7 @@ static Builder createRunnable(Runnable action) { * {@code FaultTolerance.<String>create()}. */ static Builder> create() { - return FaultToleranceSpiAccess.create(Function.identity()); + return FaultToleranceSpiAccess.get().newBuilder(Function.identity()); } /** @@ -110,7 +110,7 @@ static Builder> create() { */ static Builder, Callable>> createAsyncCallable( Callable> action) { - return FaultToleranceSpiAccess.createAsync(CompletionStage.class, ft -> ft.adaptCallable(action)); + return FaultToleranceSpiAccess.get().newAsyncBuilder(CompletionStage.class, ft -> ft.adaptCallable(action)); } /** @@ -119,7 +119,7 @@ static Builder, Callable>> createAsync */ static Builder, Supplier>> createAsyncSupplier( Supplier> action) { - return FaultToleranceSpiAccess.createAsync(CompletionStage.class, ft -> ft.adaptSupplier(action)); + return FaultToleranceSpiAccess.get().newAsyncBuilder(CompletionStage.class, ft -> ft.adaptSupplier(action)); } /** @@ -127,7 +127,7 @@ static Builder, Supplier>> createAsync * The {@code action} is asynchronous and may be offloaded to another thread. */ static Builder, Runnable> createAsyncRunnable(Runnable action) { - return FaultToleranceSpiAccess.createAsync(CompletionStage.class, ft -> ft.adaptRunnable(action)); + return FaultToleranceSpiAccess.get().newAsyncBuilder(CompletionStage.class, ft -> ft.adaptRunnable(action)); } /** @@ -139,7 +139,7 @@ static Builder, Runnable> createAsyncRunnable(Runnable act * {@code FaultTolerance.<String>createAsync()}. */ static Builder, FaultTolerance>> createAsync() { - return FaultToleranceSpiAccess.createAsync(CompletionStage.class, Function.identity()); + return FaultToleranceSpiAccess.get().newAsyncBuilder(CompletionStage.class, Function.identity()); } /** diff --git a/api/src/main/java/io/smallrye/faulttolerance/api/FaultToleranceSpiAccess.java b/api/src/main/java/io/smallrye/faulttolerance/api/FaultToleranceSpiAccess.java index fe6286b2..17cc5280 100644 --- a/api/src/main/java/io/smallrye/faulttolerance/api/FaultToleranceSpiAccess.java +++ b/api/src/main/java/io/smallrye/faulttolerance/api/FaultToleranceSpiAccess.java @@ -15,16 +15,8 @@ private static class Holder { private static final FaultToleranceSpi INSTANCE = instantiateSpi(); } - public static FaultTolerance.Builder create(Function, R> finisher) { - return Holder.INSTANCE.newBuilder(finisher); - } - - public static FaultTolerance.Builder createAsync(Class asyncType, Function, R> finisher) { - return Holder.INSTANCE.newAsyncBuilder(asyncType, finisher); - } - - static CircuitBreakerMaintenance circuitBreakerMaintenance() { - return Holder.INSTANCE.circuitBreakerMaintenance(); + public static FaultToleranceSpi get() { + return Holder.INSTANCE; } private static FaultToleranceSpi instantiateSpi() { 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 af386d50..9034c544 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 @@ -26,7 +26,7 @@ public interface MutinyFaultTolerance { * obtained from the resulting {@code Callable} is subscribed to. */ static FaultTolerance.Builder, Callable>> createCallable(Callable> action) { - return FaultToleranceSpiAccess.createAsync(Uni.class, ft -> ft.adaptCallable(action)); + return FaultToleranceSpiAccess.get().newAsyncBuilder(Uni.class, ft -> ft.adaptCallable(action)); } /** @@ -37,7 +37,7 @@ static FaultTolerance.Builder, Callable>> createCallable(Calla * obtained from the resulting {@code Supplier} is subscribed to. */ static FaultTolerance.Builder, Supplier>> createSupplier(Supplier> action) { - return FaultToleranceSpiAccess.createAsync(Uni.class, ft -> ft.adaptSupplier(action)); + return FaultToleranceSpiAccess.get().newAsyncBuilder(Uni.class, ft -> ft.adaptSupplier(action)); } /** @@ -54,6 +54,6 @@ static FaultTolerance.Builder, Supplier>> createSupplier(Suppl * {@code MutinyFaultTolerance.<String>create()}. */ static FaultTolerance.Builder, FaultTolerance>> create() { - return FaultToleranceSpiAccess.createAsync(Uni.class, Function.identity()); + return FaultToleranceSpiAccess.get().newAsyncBuilder(Uni.class, Function.identity()); } } From 555a572f6eb9ca189b831ced57fbe4515edcb63f Mon Sep 17 00:00:00 2001 From: Ladislav Thon Date: Thu, 3 Feb 2022 11:20:05 +0100 Subject: [PATCH 13/68] relax timing requirements in the programmatic API timeout test --- .../standalone/test/StandaloneTimeoutAsyncTest.java | 2 +- .../faulttolerance/standalone/test/StandaloneTimeoutTest.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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 102424ac..d6d014d9 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 @@ -29,7 +29,7 @@ public void asyncTimeout() throws Exception { .succeedsWithin(5, TimeUnit.SECONDS) .isEqualTo("fallback"); }); - assertThat(time).isCloseTo(1000, withinPercentage(20)); + assertThat(time).isCloseTo(1000, withinPercentage(50)); } public CompletionStage action() throws InterruptedException { 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 42fa81c0..76c48570 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 @@ -23,7 +23,7 @@ public void timeout() throws Exception { long time = timed(() -> { assertThat(guarded.call()).isEqualTo("fallback"); }); - assertThat(time).isCloseTo(1000, withinPercentage(20)); + assertThat(time).isCloseTo(1000, withinPercentage(50)); } public String action() throws InterruptedException { From f9cee8c1af1dbe4eed64b4268a72ee3eadac0311 Mon Sep 17 00:00:00 2001 From: Ladislav Thon Date: Thu, 3 Feb 2022 15:28:16 +0100 Subject: [PATCH 14/68] use proper descriptions of fault tolerance strategies --- .../faulttolerance/api/FaultTolerance.java | 13 ++++++ .../faulttolerance/CdiFaultTolerance.java | 29 +++++++----- .../FaultToleranceInterceptor.java | 45 +++++++------------ .../standalone/StandaloneFaultTolerance.java | 32 ++++++++----- 4 files changed, 67 insertions(+), 52 deletions(-) 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 d60c16ab..89093160 100644 --- a/api/src/main/java/io/smallrye/faulttolerance/api/FaultTolerance.java +++ b/api/src/main/java/io/smallrye/faulttolerance/api/FaultTolerance.java @@ -236,6 +236,19 @@ default Runnable adaptRunnable(Runnable action) { * @param type of result of this builder, depends on how this builder was created */ interface Builder { + /** + * Assigns a description to the resulting set of configured fault tolerance strategies. The description + * is used in logging messages and exception messages. + *

+ * 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. * diff --git a/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/CdiFaultTolerance.java b/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/CdiFaultTolerance.java index 08f5b519..b33ed7d2 100644 --- a/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/CdiFaultTolerance.java +++ b/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/CdiFaultTolerance.java @@ -74,6 +74,7 @@ static class BuilderImpl implements Builder { private final Class asyncType; // ignored when isAsync == false private final Function, R> finisher; + private String description; private BulkheadBuilderImpl bulkheadBuilder; private CircuitBreakerBuilderImpl circuitBreakerBuilder; private FallbackBuilderImpl fallbackBuilder; @@ -92,6 +93,14 @@ static class BuilderImpl implements Builder { this.isAsync = isAsync; this.asyncType = asyncType; this.finisher = finisher; + + this.description = UUID.randomUUID().toString(); + } + + @Override + public Builder withDescription(String value) { + this.description = Preconditions.checkNotNull(value, "Description must be set"); + return this; } @Override @@ -144,16 +153,16 @@ private FaultToleranceStrategy buildSyncStrategy() { FaultToleranceStrategy result = invocation(); if (ftEnabled && bulkheadBuilder != null) { - result = new SemaphoreBulkhead<>(result, "unknown", bulkheadBuilder.limit); + result = new SemaphoreBulkhead<>(result, description, bulkheadBuilder.limit); } if (ftEnabled && timeoutBuilder != null) { - result = new Timeout<>(result, "unknown", timeoutBuilder.durationInMillis, + result = new Timeout<>(result, description, timeoutBuilder.durationInMillis, new TimerTimeoutWatcher(timer)); } if (ftEnabled && circuitBreakerBuilder != null) { - result = new CircuitBreaker<>(result, "unknown", + result = new CircuitBreaker<>(result, description, createExceptionDecision(circuitBreakerBuilder.skipOn, circuitBreakerBuilder.failOn), circuitBreakerBuilder.delayInMillis, circuitBreakerBuilder.requestVolumeThreshold, @@ -168,7 +177,7 @@ private FaultToleranceStrategy buildSyncStrategy() { if (ftEnabled && retryBuilder != null) { Supplier backoff = prepareRetryBackoff(retryBuilder); - result = new Retry<>(result, "unknown", + result = new Retry<>(result, description, createExceptionDecision(retryBuilder.abortOn, retryBuilder.retryOn), retryBuilder.maxRetries, retryBuilder.maxDurationInMillis, () -> new ThreadSleepDelay(backoff.get()), new SystemStopwatch()); @@ -177,7 +186,7 @@ private FaultToleranceStrategy buildSyncStrategy() { // fallback is always enabled if (fallbackBuilder != null) { FallbackFunction fallbackFunction = ctx -> fallbackBuilder.handler.apply(ctx.failure); - result = new Fallback<>(result, "unknown", fallbackFunction, + result = new Fallback<>(result, description, fallbackFunction, createExceptionDecision(fallbackBuilder.skipOn, fallbackBuilder.applyOn)); } @@ -210,17 +219,17 @@ private FaultToleranceStrategy> buildCompletionStageChain( result = new CompletionStageExecution<>(result, executor); if (ftEnabled && bulkheadBuilder != null) { - result = new CompletionStageThreadPoolBulkhead<>(result, "unknown", bulkheadBuilder.limit, + result = new CompletionStageThreadPoolBulkhead<>(result, description, bulkheadBuilder.limit, bulkheadBuilder.queueSize); } if (ftEnabled && timeoutBuilder != null) { - result = new CompletionStageTimeout<>(result, "unknown", timeoutBuilder.durationInMillis, + result = new CompletionStageTimeout<>(result, description, timeoutBuilder.durationInMillis, new TimerTimeoutWatcher(timer)); } if (ftEnabled && circuitBreakerBuilder != null) { - result = new CompletionStageCircuitBreaker<>(result, "unknown", + result = new CompletionStageCircuitBreaker<>(result, description, createExceptionDecision(circuitBreakerBuilder.skipOn, circuitBreakerBuilder.failOn), circuitBreakerBuilder.delayInMillis, circuitBreakerBuilder.requestVolumeThreshold, @@ -235,7 +244,7 @@ private FaultToleranceStrategy> buildCompletionStageChain( if (ftEnabled && retryBuilder != null) { Supplier backoff = prepareRetryBackoff(retryBuilder); - result = new CompletionStageRetry<>(result, "unknown", + result = new CompletionStageRetry<>(result, description, createExceptionDecision(retryBuilder.abortOn, retryBuilder.retryOn), retryBuilder.maxRetries, retryBuilder.maxDurationInMillis, () -> new TimerDelay(backoff.get(), timer), new SystemStopwatch()); @@ -245,7 +254,7 @@ private FaultToleranceStrategy> buildCompletionStageChain( if (fallbackBuilder != null) { FallbackFunction> fallbackFunction = ctx -> AsyncTypes.toCompletionStageIfRequired( fallbackBuilder.handler.apply(ctx.failure), asyncType); - result = new CompletionStageFallback<>(result, "unknown", fallbackFunction, + result = new CompletionStageFallback<>(result, description, fallbackFunction, createExceptionDecision(fallbackBuilder.skipOn, fallbackBuilder.applyOn)); } 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 0b745f4b..475ab18c 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 @@ -237,20 +237,18 @@ private FaultToleranceStrategy> prepareComplectionStageCh if (operation.hasBulkhead()) { int size = operation.getBulkhead().value(); int queueSize = operation.getBulkhead().waitingTaskQueue(); - result = new CompletionStageThreadPoolBulkhead<>(result, "Bulkhead[" + point + "]", - size, queueSize); + result = new CompletionStageThreadPoolBulkhead<>(result, point.toString(), size, queueSize); } if (operation.hasTimeout()) { long timeoutMs = getTimeInMs(operation.getTimeout().value(), operation.getTimeout().unit()); - result = new CompletionStageTimeout<>(result, "Timeout[" + point + "]", - timeoutMs, + result = new CompletionStageTimeout<>(result, point.toString(), timeoutMs, new TimerTimeoutWatcher(timer)); } if (operation.hasCircuitBreaker()) { long delayInMillis = getTimeInMs(operation.getCircuitBreaker().delay(), operation.getCircuitBreaker().delayUnit()); - result = new CompletionStageCircuitBreaker<>(result, "CircuitBreaker[" + point + "]", + result = new CompletionStageCircuitBreaker<>(result, point.toString(), createExceptionDecision(operation.getCircuitBreaker().skipOn(), operation.getCircuitBreaker().failOn()), delayInMillis, operation.getCircuitBreaker().requestVolumeThreshold(), @@ -269,8 +267,7 @@ private FaultToleranceStrategy> prepareComplectionStageCh Supplier backoff = prepareRetryBackoff(operation); - result = new CompletionStageRetry<>(result, - "Retry[" + point + "]", + result = new CompletionStageRetry<>(result, point.toString(), createExceptionDecision(operation.getRetry().abortOn(), operation.getRetry().retryOn()), operation.getRetry().maxRetries(), maxDurationMs, @@ -279,9 +276,7 @@ private FaultToleranceStrategy> prepareComplectionStageCh } if (operation.hasFallback()) { - result = new CompletionStageFallback<>( - result, - "Fallback[" + point + "]", + result = new CompletionStageFallback<>(result, point.toString(), prepareFallbackFunction(point, operation), createExceptionDecision(operation.getFallback().skipOn(), operation.getFallback().applyOn())); } @@ -301,21 +296,18 @@ private FaultToleranceStrategy prepareSyncStrategy(FaultToleranceOperatio FaultToleranceStrategy result = invocation(); if (operation.hasBulkhead()) { - result = new SemaphoreBulkhead<>(result, - "Bulkhead[" + point + "]", - operation.getBulkhead().value()); + result = new SemaphoreBulkhead<>(result, point.toString(), operation.getBulkhead().value()); } if (operation.hasTimeout()) { long timeoutMs = getTimeInMs(operation.getTimeout().value(), operation.getTimeout().unit()); - result = new Timeout<>(result, "Timeout[" + point + "]", - timeoutMs, + result = new Timeout<>(result, point.toString(), timeoutMs, new TimerTimeoutWatcher(timer)); } if (operation.hasCircuitBreaker()) { long delayInMillis = getTimeInMs(operation.getCircuitBreaker().delay(), operation.getCircuitBreaker().delayUnit()); - result = new CircuitBreaker<>(result, "CircuitBreaker[" + point + "]", + result = new CircuitBreaker<>(result, point.toString(), createExceptionDecision(operation.getCircuitBreaker().skipOn(), operation.getCircuitBreaker().failOn()), delayInMillis, operation.getCircuitBreaker().requestVolumeThreshold(), @@ -334,8 +326,7 @@ private FaultToleranceStrategy prepareSyncStrategy(FaultToleranceOperatio Supplier backoff = prepareRetryBackoff(operation); - result = new Retry<>(result, - "Retry[" + point + "]", + result = new Retry<>(result, point.toString(), createExceptionDecision(operation.getRetry().abortOn(), operation.getRetry().retryOn()), operation.getRetry().maxRetries(), maxDurationMs, @@ -344,9 +335,7 @@ private FaultToleranceStrategy prepareSyncStrategy(FaultToleranceOperatio } if (operation.hasFallback()) { - result = new Fallback<>( - result, - "Fallback[" + point + "]", + result = new Fallback<>(result, point.toString(), prepareFallbackFunction(point, operation), createExceptionDecision(operation.getFallback().skipOn(), operation.getFallback().applyOn())); } @@ -367,21 +356,19 @@ private FaultToleranceStrategy> prepareFutureStrategy(FaultToleran if (operation.hasBulkhead()) { int size = operation.getBulkhead().value(); int queueSize = operation.getBulkhead().waitingTaskQueue(); - result = new FutureThreadPoolBulkhead<>(result, "Bulkhead[" + point + "]", - size, queueSize); + result = new FutureThreadPoolBulkhead<>(result, point.toString(), size, queueSize); } if (operation.hasTimeout()) { long timeoutMs = getTimeInMs(operation.getTimeout().value(), operation.getTimeout().unit()); - Timeout> timeout = new Timeout<>(result, "Timeout[" + point + "]", - timeoutMs, + Timeout> timeout = new Timeout<>(result, point.toString(), timeoutMs, new TimerTimeoutWatcher(timer)); result = new AsyncTimeout<>(timeout, asyncExecutor); } if (operation.hasCircuitBreaker()) { long delayInMillis = getTimeInMs(operation.getCircuitBreaker().delay(), operation.getCircuitBreaker().delayUnit()); - result = new CircuitBreaker<>(result, "CircuitBreaker[" + point + "]", + result = new CircuitBreaker<>(result, point.toString(), createExceptionDecision(operation.getCircuitBreaker().skipOn(), operation.getCircuitBreaker().failOn()), delayInMillis, operation.getCircuitBreaker().requestVolumeThreshold(), @@ -400,8 +387,7 @@ private FaultToleranceStrategy> prepareFutureStrategy(FaultToleran Supplier backoff = prepareRetryBackoff(operation); - result = new Retry<>(result, - "Retry[" + point + "]", + result = new Retry<>(result, point.toString(), createExceptionDecision(operation.getRetry().abortOn(), operation.getRetry().retryOn()), operation.getRetry().maxRetries(), maxDurationMs, @@ -410,8 +396,7 @@ private FaultToleranceStrategy> prepareFutureStrategy(FaultToleran } if (operation.hasFallback()) { - result = new Fallback<>(result, - "Fallback[" + point + "]", + result = new Fallback<>(result, point.toString(), prepareFallbackFunction(point, operation), createExceptionDecision(operation.getFallback().skipOn(), operation.getFallback().applyOn())); } diff --git a/implementation/standalone/src/main/java/io/smallrye/faulttolerance/standalone/StandaloneFaultTolerance.java b/implementation/standalone/src/main/java/io/smallrye/faulttolerance/standalone/StandaloneFaultTolerance.java index 3dd9135b..5c0eab45 100644 --- a/implementation/standalone/src/main/java/io/smallrye/faulttolerance/standalone/StandaloneFaultTolerance.java +++ b/implementation/standalone/src/main/java/io/smallrye/faulttolerance/standalone/StandaloneFaultTolerance.java @@ -74,6 +74,7 @@ static class BuilderImpl implements Builder { private final Class asyncType; // ignored when isAsync == false private final Function, R> finisher; + private String description; private BulkheadBuilderImpl bulkheadBuilder; private CircuitBreakerBuilderImpl circuitBreakerBuilder; private FallbackBuilderImpl fallbackBuilder; @@ -92,6 +93,14 @@ static class BuilderImpl implements Builder { this.isAsync = isAsync; this.asyncType = asyncType; this.finisher = finisher; + + this.description = UUID.randomUUID().toString(); + } + + @Override + public Builder withDescription(String value) { + this.description = Preconditions.checkNotNull(value, "Description must be set"); + return this; } @Override @@ -144,16 +153,16 @@ private FaultToleranceStrategy buildSyncStrategy() { FaultToleranceStrategy result = invocation(); if (ftEnabled && bulkheadBuilder != null) { - result = new SemaphoreBulkhead<>(result, "unknown", bulkheadBuilder.limit); + result = new SemaphoreBulkhead<>(result, description, bulkheadBuilder.limit); } if (ftEnabled && timeoutBuilder != null) { - result = new Timeout<>(result, "unknown", timeoutBuilder.durationInMillis, + result = new Timeout<>(result, description, timeoutBuilder.durationInMillis, new TimerTimeoutWatcher(timer)); } if (ftEnabled && circuitBreakerBuilder != null) { - result = new CircuitBreaker<>(result, "unknown", + result = new CircuitBreaker<>(result, description, createExceptionDecision(circuitBreakerBuilder.skipOn, circuitBreakerBuilder.failOn), circuitBreakerBuilder.delayInMillis, circuitBreakerBuilder.requestVolumeThreshold, @@ -168,7 +177,7 @@ private FaultToleranceStrategy buildSyncStrategy() { if (ftEnabled && retryBuilder != null) { Supplier backoff = prepareRetryBackoff(retryBuilder); - result = new Retry<>(result, "unknown", + result = new Retry<>(result, description, createExceptionDecision(retryBuilder.abortOn, retryBuilder.retryOn), retryBuilder.maxRetries, retryBuilder.maxDurationInMillis, () -> new ThreadSleepDelay(backoff.get()), new SystemStopwatch()); @@ -177,7 +186,7 @@ private FaultToleranceStrategy buildSyncStrategy() { // fallback is always enabled if (fallbackBuilder != null) { FallbackFunction fallbackFunction = ctx -> fallbackBuilder.handler.apply(ctx.failure); - result = new Fallback<>(result, "unknown", fallbackFunction, + result = new Fallback<>(result, description, fallbackFunction, createExceptionDecision(fallbackBuilder.skipOn, fallbackBuilder.applyOn)); } @@ -210,17 +219,17 @@ private FaultToleranceStrategy> buildCompletionStageChain( result = new CompletionStageExecution<>(result, executor); if (ftEnabled && bulkheadBuilder != null) { - result = new CompletionStageThreadPoolBulkhead<>(result, "unknown", bulkheadBuilder.limit, + result = new CompletionStageThreadPoolBulkhead<>(result, description, bulkheadBuilder.limit, bulkheadBuilder.queueSize); } if (ftEnabled && timeoutBuilder != null) { - result = new CompletionStageTimeout<>(result, "unknown", timeoutBuilder.durationInMillis, + result = new CompletionStageTimeout<>(result, description, timeoutBuilder.durationInMillis, new TimerTimeoutWatcher(timer)); } if (ftEnabled && circuitBreakerBuilder != null) { - result = new CompletionStageCircuitBreaker<>(result, "unknown", + result = new CompletionStageCircuitBreaker<>(result, description, createExceptionDecision(circuitBreakerBuilder.skipOn, circuitBreakerBuilder.failOn), circuitBreakerBuilder.delayInMillis, circuitBreakerBuilder.requestVolumeThreshold, @@ -228,14 +237,13 @@ private FaultToleranceStrategy> buildCompletionStageChain( circuitBreakerBuilder.successThreshold, new SystemStopwatch()); - String cbName = circuitBreakerBuilder.name != null ? circuitBreakerBuilder.name : UUID.randomUUID().toString(); - cbMaintenance.register(cbName, (CircuitBreaker) result); + cbMaintenance.register(description, (CircuitBreaker) result); } if (ftEnabled && retryBuilder != null) { Supplier backoff = prepareRetryBackoff(retryBuilder); - result = new CompletionStageRetry<>(result, "unknown", + result = new CompletionStageRetry<>(result, description, createExceptionDecision(retryBuilder.abortOn, retryBuilder.retryOn), retryBuilder.maxRetries, retryBuilder.maxDurationInMillis, () -> new TimerDelay(backoff.get(), timer), new SystemStopwatch()); @@ -245,7 +253,7 @@ private FaultToleranceStrategy> buildCompletionStageChain( if (fallbackBuilder != null) { FallbackFunction> fallbackFunction = ctx -> AsyncTypes.toCompletionStageIfRequired( fallbackBuilder.handler.apply(ctx.failure), asyncType); - result = new CompletionStageFallback<>(result, "unknown", fallbackFunction, + result = new CompletionStageFallback<>(result, description, fallbackFunction, createExceptionDecision(fallbackBuilder.skipOn, fallbackBuilder.applyOn)); } From 7a592a69b817768ee94a7e1c1d04b4ef310b419d Mon Sep 17 00:00:00 2001 From: Ladislav Thon Date: Fri, 4 Feb 2022 12:44:59 +0100 Subject: [PATCH 15/68] add events to the programmatic API This commit also gets rid of a massive code duplication between the standalone and CDI implementations. Most of the programmatic API implementation code is now shared between the two. --- .../faulttolerance/api/FaultTolerance.java | 129 +++- .../api/FaultToleranceSpiAccess.java | 1 - implementation/core/pom.xml | 5 +- .../CircuitBreakerMaintenanceInternal.java | 8 + .../core/apiimpl/EventHandlers.java | 154 ++++ .../core/apiimpl/FaultToleranceImpl.java} | 126 +++- .../circuit/breaker/CircuitBreakerEvents.java | 17 +- .../core/metrics/MetricsCollector.java | 13 +- .../faulttolerance/CdiFaultTolerance.java | 657 ------------------ .../faulttolerance/CdiFaultToleranceSpi.java | 5 +- .../CircuitBreakerMaintenanceImpl.java | 7 +- implementation/standalone/pom.xml | 5 + .../StandaloneCircuitBreakerMaintenance.java | 7 +- .../StandaloneFaultToleranceSpi.java | 5 +- .../StandaloneBulkheadAsyncEventsTest.java | 71 ++ .../test/StandaloneBulkheadEventsTest.java | 84 +++ ...andaloneCircuitBreakerAsyncEventsTest.java | 90 +++ .../StandaloneCircuitBreakerEventsTest.java | 79 +++ .../test/StandaloneRetryAsyncEventsTest.java | 65 ++ .../test/StandaloneRetryEventsTest.java | 57 ++ .../StandaloneTimeoutAsyncEventsTest.java | 60 ++ .../test/StandaloneTimeoutEventsTest.java | 52 ++ .../CdiBulkheadAsyncEventsTest.java | 8 + .../programmatic/CdiBulkheadEventsTest.java | 8 + .../CdiCircuitBreakerAsyncEventsTest.java | 8 + .../CdiCircuitBreakerEventsTest.java | 8 + .../programmatic/CdiRetryAsyncEventsTest.java | 8 + .../programmatic/CdiRetryEventsTest.java | 8 + .../CdiTimeoutAsyncEventsTest.java | 8 + .../programmatic/CdiTimeoutEventsTest.java | 8 + 30 files changed, 1064 insertions(+), 697 deletions(-) create mode 100644 implementation/core/src/main/java/io/smallrye/faulttolerance/core/apiimpl/CircuitBreakerMaintenanceInternal.java create mode 100644 implementation/core/src/main/java/io/smallrye/faulttolerance/core/apiimpl/EventHandlers.java rename implementation/{standalone/src/main/java/io/smallrye/faulttolerance/standalone/StandaloneFaultTolerance.java => core/src/main/java/io/smallrye/faulttolerance/core/apiimpl/FaultToleranceImpl.java} (83%) delete mode 100644 implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/CdiFaultTolerance.java create mode 100644 implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneBulkheadAsyncEventsTest.java create mode 100644 implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneBulkheadEventsTest.java create mode 100644 implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneCircuitBreakerAsyncEventsTest.java create mode 100644 implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneCircuitBreakerEventsTest.java create mode 100644 implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneRetryAsyncEventsTest.java create mode 100644 implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneRetryEventsTest.java create mode 100644 implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneTimeoutAsyncEventsTest.java create mode 100644 implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneTimeoutEventsTest.java create mode 100644 testsuite/basic/src/test/java/io/smallrye/faulttolerance/programmatic/CdiBulkheadAsyncEventsTest.java create mode 100644 testsuite/basic/src/test/java/io/smallrye/faulttolerance/programmatic/CdiBulkheadEventsTest.java create mode 100644 testsuite/basic/src/test/java/io/smallrye/faulttolerance/programmatic/CdiCircuitBreakerAsyncEventsTest.java create mode 100644 testsuite/basic/src/test/java/io/smallrye/faulttolerance/programmatic/CdiCircuitBreakerEventsTest.java create mode 100644 testsuite/basic/src/test/java/io/smallrye/faulttolerance/programmatic/CdiRetryAsyncEventsTest.java create mode 100644 testsuite/basic/src/test/java/io/smallrye/faulttolerance/programmatic/CdiRetryEventsTest.java create mode 100644 testsuite/basic/src/test/java/io/smallrye/faulttolerance/programmatic/CdiTimeoutAsyncEventsTest.java create mode 100644 testsuite/basic/src/test/java/io/smallrye/faulttolerance/programmatic/CdiTimeoutEventsTest.java 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 89093160..30d78485 100644 --- a/api/src/main/java/io/smallrye/faulttolerance/api/FaultTolerance.java +++ b/api/src/main/java/io/smallrye/faulttolerance/api/FaultTolerance.java @@ -357,6 +357,38 @@ interface BulkheadBuilder { */ BulkheadBuilder queueSize(int value); + /** + * 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. * @@ -455,7 +487,8 @@ default CircuitBreakerBuilder skipOn(Class value) { /** * Sets a circuit breaker name. Required to use the {@link CircuitBreakerMaintenance} methods - * that accept a name parameter. Defaults to unnamed. + * that accept a name parameter. Defaults to unnamed. It is an error to use the same name + * for multiple circuit breakers. * * @param value the circuit breaker name, must not be {@code null} * @return this circuit breaker builder @@ -463,6 +496,47 @@ default CircuitBreakerBuilder skipOn(Class value) { */ 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. * @@ -673,6 +747,39 @@ default RetryBuilder abortOn(Class value) { */ 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. * @@ -797,6 +904,26 @@ interface TimeoutBuilder { */ 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. * diff --git a/api/src/main/java/io/smallrye/faulttolerance/api/FaultToleranceSpiAccess.java b/api/src/main/java/io/smallrye/faulttolerance/api/FaultToleranceSpiAccess.java index 17cc5280..be769510 100644 --- a/api/src/main/java/io/smallrye/faulttolerance/api/FaultToleranceSpiAccess.java +++ b/api/src/main/java/io/smallrye/faulttolerance/api/FaultToleranceSpiAccess.java @@ -1,7 +1,6 @@ package io.smallrye.faulttolerance.api; import java.util.ServiceLoader; -import java.util.function.Function; import io.smallrye.common.annotation.Experimental; diff --git a/implementation/core/pom.xml b/implementation/core/pom.xml index fb4ae933..b5e98d9a 100644 --- a/implementation/core/pom.xml +++ b/implementation/core/pom.xml @@ -47,6 +47,10 @@ org.eclipse.microprofile.fault-tolerance microprofile-fault-tolerance-api + + io.smallrye + smallrye-fault-tolerance-api + org.jboss.logging jboss-logging @@ -70,7 +74,6 @@ assertj-core test - org.awaitility awaitility diff --git a/implementation/core/src/main/java/io/smallrye/faulttolerance/core/apiimpl/CircuitBreakerMaintenanceInternal.java b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/apiimpl/CircuitBreakerMaintenanceInternal.java new file mode 100644 index 00000000..2265c14c --- /dev/null +++ b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/apiimpl/CircuitBreakerMaintenanceInternal.java @@ -0,0 +1,8 @@ +package io.smallrye.faulttolerance.core.apiimpl; + +import io.smallrye.faulttolerance.api.CircuitBreakerMaintenance; +import io.smallrye.faulttolerance.core.circuit.breaker.CircuitBreaker; + +public interface CircuitBreakerMaintenanceInternal extends CircuitBreakerMaintenance { + void register(String circuitBreakerName, CircuitBreaker circuitBreaker); +} diff --git a/implementation/core/src/main/java/io/smallrye/faulttolerance/core/apiimpl/EventHandlers.java b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/apiimpl/EventHandlers.java new file mode 100644 index 00000000..47a0411a --- /dev/null +++ b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/apiimpl/EventHandlers.java @@ -0,0 +1,154 @@ +package io.smallrye.faulttolerance.core.apiimpl; + +import java.util.function.Consumer; + +import io.smallrye.faulttolerance.api.CircuitBreakerState; +import io.smallrye.faulttolerance.core.InvocationContext; +import io.smallrye.faulttolerance.core.bulkhead.BulkheadEvents; +import io.smallrye.faulttolerance.core.circuit.breaker.CircuitBreakerEvents; +import io.smallrye.faulttolerance.core.retry.RetryEvents; +import io.smallrye.faulttolerance.core.timeout.TimeoutEvents; + +final class EventHandlers { + private final Runnable bulkheadOnAccepted; + private final Runnable bulkheadOnRejected; + private final Runnable bulkheadOnFinished; + + private final Consumer circuitBreakerOnStateChange; + private final Runnable circuitBreakerOnSuccess; + private final Runnable circuitBreakerOnFailure; + private final Runnable circuitBreakerOnPrevented; + + private final Runnable retryOnRetry; + private final Runnable retryOnSuccess; + private final Runnable retryOnFailure; + + private final Runnable timeoutOnTimeout; + private final Runnable timeoutOnFinished; + + EventHandlers(Runnable bulkheadOnAccepted, Runnable bulkheadOnRejected, Runnable bulkheadOnFinished, + Consumer circuitBreakerOnStateChange, Runnable circuitBreakerOnSuccess, + Runnable circuitBreakerOnFailure, Runnable circuitBreakerOnPrevented, Runnable retryOnRetry, + Runnable retryOnSuccess, Runnable retryOnFailure, Runnable timeoutOnTimeout, Runnable timeoutOnFinished) { + this.bulkheadOnAccepted = wrap(bulkheadOnAccepted); + this.bulkheadOnRejected = wrap(bulkheadOnRejected); + this.bulkheadOnFinished = wrap(bulkheadOnFinished); + this.circuitBreakerOnStateChange = wrap(circuitBreakerOnStateChange); + this.circuitBreakerOnSuccess = wrap(circuitBreakerOnSuccess); + this.circuitBreakerOnFailure = wrap(circuitBreakerOnFailure); + this.circuitBreakerOnPrevented = wrap(circuitBreakerOnPrevented); + this.retryOnRetry = wrap(retryOnRetry); + this.retryOnSuccess = wrap(retryOnSuccess); + this.retryOnFailure = wrap(retryOnFailure); + this.timeoutOnTimeout = wrap(timeoutOnTimeout); + this.timeoutOnFinished = wrap(timeoutOnFinished); + } + + private static Consumer wrap(Consumer callback) { + if (callback == null) { + return null; + } + + return value -> { + try { + callback.accept(value); + } catch (Exception ignored) { + } + }; + } + + private static Runnable wrap(Runnable callback) { + if (callback == null) { + return null; + } + + return () -> { + try { + callback.run(); + } catch (Exception ignored) { + } + }; + } + + void register(InvocationContext ctx) { + if (bulkheadOnAccepted != null || bulkheadOnRejected != null) { + ctx.registerEventHandler(BulkheadEvents.DecisionMade.class, event -> { + if (event.accepted) { + if (bulkheadOnAccepted != null) { + bulkheadOnAccepted.run(); + } + } else { + if (bulkheadOnRejected != null) { + bulkheadOnRejected.run(); + } + } + }); + } + if (bulkheadOnFinished != null) { + ctx.registerEventHandler(BulkheadEvents.FinishedRunning.class, event -> { + bulkheadOnFinished.run(); + }); + } + + if (circuitBreakerOnStateChange != null) { + ctx.registerEventHandler(CircuitBreakerEvents.StateTransition.class, event -> { + CircuitBreakerState targetState = event.targetState; + circuitBreakerOnStateChange.accept(targetState); + }); + } + if (circuitBreakerOnSuccess != null || circuitBreakerOnFailure != null || circuitBreakerOnPrevented != null) { + ctx.registerEventHandler(CircuitBreakerEvents.Finished.class, event -> { + switch (event.result) { + case SUCCESS: + if (circuitBreakerOnSuccess != null) { + circuitBreakerOnSuccess.run(); + } + break; + case FAILURE: + if (circuitBreakerOnFailure != null) { + circuitBreakerOnFailure.run(); + } + break; + case PREVENTED: + if (circuitBreakerOnPrevented != null) { + circuitBreakerOnPrevented.run(); + } + break; + } + }); + } + + if (retryOnRetry != null) { + ctx.registerEventHandler(RetryEvents.Retried.class, event -> { + retryOnRetry.run(); + }); + } + if (retryOnSuccess != null || retryOnFailure != null) { + ctx.registerEventHandler(RetryEvents.Finished.class, event -> { + if (event.result == RetryEvents.Result.VALUE_RETURNED) { + if (retryOnSuccess != null) { + retryOnSuccess.run(); + } + } else { + if (retryOnFailure != null) { + retryOnFailure.run(); + } + } + }); + } + + if (timeoutOnTimeout != null || timeoutOnFinished != null) { + ctx.registerEventHandler(TimeoutEvents.Finished.class, event -> { + if (event.timedOut) { + if (timeoutOnTimeout != null) { + timeoutOnTimeout.run(); + } + } else { + if (timeoutOnFinished != null) { + timeoutOnFinished.run(); + } + } + }); + } + } +} diff --git a/implementation/standalone/src/main/java/io/smallrye/faulttolerance/standalone/StandaloneFaultTolerance.java b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/apiimpl/FaultToleranceImpl.java similarity index 83% rename from implementation/standalone/src/main/java/io/smallrye/faulttolerance/standalone/StandaloneFaultTolerance.java rename to implementation/core/src/main/java/io/smallrye/faulttolerance/core/apiimpl/FaultToleranceImpl.java index 5c0eab45..d83b6c25 100644 --- a/implementation/standalone/src/main/java/io/smallrye/faulttolerance/standalone/StandaloneFaultTolerance.java +++ b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/apiimpl/FaultToleranceImpl.java @@ -1,4 +1,4 @@ -package io.smallrye.faulttolerance.standalone; +package io.smallrye.faulttolerance.core.apiimpl; import static io.smallrye.faulttolerance.core.Invocation.invocation; @@ -10,9 +10,11 @@ import java.util.concurrent.Callable; import java.util.concurrent.CompletionStage; import java.util.concurrent.Executor; +import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Supplier; +import io.smallrye.faulttolerance.api.CircuitBreakerState; import io.smallrye.faulttolerance.api.CustomBackoffStrategy; import io.smallrye.faulttolerance.api.FaultTolerance; import io.smallrye.faulttolerance.core.FaultToleranceStrategy; @@ -50,26 +52,28 @@ import io.smallrye.faulttolerance.core.util.Preconditions; import io.smallrye.faulttolerance.core.util.SetOfThrowables; -// this is mostly a copy of CdiFaultTolerance and they must be kept in sync -class StandaloneFaultTolerance implements FaultTolerance { +public final class FaultToleranceImpl implements FaultTolerance { private final FaultToleranceStrategy strategy; + private final EventHandlers eventHandlers; - StandaloneFaultTolerance(FaultToleranceStrategy strategy) { + FaultToleranceImpl(FaultToleranceStrategy strategy, EventHandlers eventHandlers) { this.strategy = strategy; + this.eventHandlers = eventHandlers; } @Override public T call(Callable action) throws Exception { InvocationContext ctx = new InvocationContext<>(action); + eventHandlers.register(ctx); return strategy.apply(ctx); } - static class BuilderImpl implements Builder { + public static final class BuilderImpl implements Builder { private final boolean ftEnabled; private final Executor executor; private final Timer timer; private final EventLoop eventLoop; - private final StandaloneCircuitBreakerMaintenance cbMaintenance; + private final CircuitBreakerMaintenanceInternal cbMaintenance; private final boolean isAsync; private final Class asyncType; // ignored when isAsync == false private final Function, R> finisher; @@ -82,8 +86,8 @@ static class BuilderImpl implements Builder { private TimeoutBuilderImpl timeoutBuilder; private boolean offloadToAnotherThread; - BuilderImpl(boolean ftEnabled, Executor executor, Timer timer, EventLoop eventLoop, - StandaloneCircuitBreakerMaintenance cbMaintenance, boolean isAsync, Class asyncType, + public BuilderImpl(boolean ftEnabled, Executor executor, Timer timer, EventLoop eventLoop, + CircuitBreakerMaintenanceInternal cbMaintenance, boolean isAsync, Class asyncType, Function, R> finisher) { this.ftEnabled = ftEnabled; this.executor = executor; @@ -140,11 +144,25 @@ public Builder withThreadOffload(boolean value) { @Override public R build() { + EventHandlers eventHandlers = new EventHandlers( + bulkheadBuilder != null ? bulkheadBuilder.onAccepted : null, + bulkheadBuilder != null ? bulkheadBuilder.onRejected : null, + bulkheadBuilder != null ? bulkheadBuilder.onFinished : null, + circuitBreakerBuilder != null ? circuitBreakerBuilder.onStateChange : null, + circuitBreakerBuilder != null ? circuitBreakerBuilder.onSuccess : null, + circuitBreakerBuilder != null ? circuitBreakerBuilder.onFailure : null, + circuitBreakerBuilder != null ? circuitBreakerBuilder.onPrevented : 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); + FaultTolerance result; if (isAsync) { - result = new StandaloneFaultTolerance(buildAsyncStrategy()); + result = new FaultToleranceImpl(buildAsyncStrategy(), eventHandlers); } else { - result = new StandaloneFaultTolerance<>(buildSyncStrategy()); + result = new FaultToleranceImpl<>(buildSyncStrategy(), eventHandlers); } return finisher.apply(result); } @@ -308,6 +326,10 @@ static class BulkheadBuilderImpl implements BulkheadBuilder { private int limit = 10; private int queueSize = 10; + private Runnable onAccepted; + private Runnable onRejected; + private Runnable onFinished; + BulkheadBuilderImpl(BuilderImpl parent) { this.parent = parent; } @@ -328,6 +350,24 @@ public BulkheadBuilder queueSize(int value) { 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; @@ -347,6 +387,11 @@ static class CircuitBreakerBuilderImpl implements CircuitBreakerBuilder onStateChange; + private Runnable onSuccess; + private Runnable onFailure; + private Runnable onPrevented; + CircuitBreakerBuilderImpl(BuilderImpl parent) { this.parent = parent; } @@ -396,6 +441,30 @@ public CircuitBreakerBuilder name(String value) { 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() { parent.circuitBreakerBuilder = this; @@ -462,6 +531,10 @@ static class RetryBuilderImpl implements RetryBuilder { private FibonacciBackoffBuilderImpl fibonacciBackoffBuilder; private CustomBackoffBuilderImpl customBackoffBuilder; + private Runnable onRetry; + private Runnable onSuccess; + private Runnable onFailure; + RetryBuilderImpl(BuilderImpl parent) { this.parent = parent; } @@ -526,6 +599,24 @@ 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() { int backoffStrategies = 0; @@ -633,6 +724,9 @@ static class TimeoutBuilderImpl implements TimeoutBuilder { private long durationInMillis = 1000; + private Runnable onTimeout; + private Runnable onFinished; + TimeoutBuilderImpl(BuilderImpl parent) { this.parent = parent; } @@ -646,6 +740,18 @@ public TimeoutBuilder duration(long value, ChronoUnit 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; diff --git a/implementation/core/src/main/java/io/smallrye/faulttolerance/core/circuit/breaker/CircuitBreakerEvents.java b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/circuit/breaker/CircuitBreakerEvents.java index 1aa85c10..3507b3df 100644 --- a/implementation/core/src/main/java/io/smallrye/faulttolerance/core/circuit/breaker/CircuitBreakerEvents.java +++ b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/circuit/breaker/CircuitBreakerEvents.java @@ -1,14 +1,9 @@ package io.smallrye.faulttolerance.core.circuit.breaker; +import io.smallrye.faulttolerance.api.CircuitBreakerState; import io.smallrye.faulttolerance.core.InvocationContextEvent; public class CircuitBreakerEvents { - public enum State { - CLOSED, - OPEN, - HALF_OPEN, - } - public enum Result { SUCCESS, FAILURE, @@ -16,14 +11,14 @@ public enum Result { } public enum StateTransition implements InvocationContextEvent { - TO_CLOSED(State.CLOSED), - TO_OPEN(State.OPEN), - TO_HALF_OPEN(State.HALF_OPEN), + TO_CLOSED(CircuitBreakerState.CLOSED), + TO_OPEN(CircuitBreakerState.OPEN), + TO_HALF_OPEN(CircuitBreakerState.HALF_OPEN), ; - public final State targetState; + public final CircuitBreakerState targetState; - StateTransition(State targetState) { + StateTransition(CircuitBreakerState targetState) { this.targetState = targetState; } } 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 51f0e837..ce18c0ee 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 @@ -5,6 +5,7 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicLong; +import io.smallrye.faulttolerance.api.CircuitBreakerState; import io.smallrye.faulttolerance.core.FaultToleranceStrategy; import io.smallrye.faulttolerance.core.InvocationContext; import io.smallrye.faulttolerance.core.bulkhead.BulkheadEvents; @@ -23,7 +24,7 @@ public class MetricsCollector implements FaultToleranceStrategy { // circuit breaker - private volatile CircuitBreakerEvents.State state; + private volatile CircuitBreakerState state; private final AtomicLong previousHalfOpenTime = new AtomicLong(); private volatile long halfOpenStart; private final AtomicLong previousClosedTime = new AtomicLong(); @@ -41,15 +42,15 @@ public MetricsCollector(FaultToleranceStrategy delegate, MetricsRecorder metr this.metrics = metrics; this.isAsync = isAsync; - this.state = CircuitBreakerEvents.State.CLOSED; + this.state = CircuitBreakerState.CLOSED; this.closedStart = System.nanoTime(); metrics.registerCircuitBreakerTimeSpentInClosed( - () -> getTime(CircuitBreakerEvents.State.CLOSED, closedStart, previousClosedTime)); + () -> getTime(CircuitBreakerState.CLOSED, closedStart, previousClosedTime)); metrics.registerCircuitBreakerTimeSpentInOpen( - () -> getTime(CircuitBreakerEvents.State.OPEN, openStart, previousOpenTime)); + () -> getTime(CircuitBreakerState.OPEN, openStart, previousOpenTime)); metrics.registerCircuitBreakerTimeSpentInHalfOpen( - () -> getTime(CircuitBreakerEvents.State.HALF_OPEN, halfOpenStart, previousHalfOpenTime)); + () -> getTime(CircuitBreakerState.HALF_OPEN, halfOpenStart, previousHalfOpenTime)); metrics.registerBulkheadExecutionsRunning(runningExecutions::get); if (isAsync) { @@ -57,7 +58,7 @@ public MetricsCollector(FaultToleranceStrategy delegate, MetricsRecorder metr } } - private Long getTime(CircuitBreakerEvents.State measuredState, long measuredStateStart, AtomicLong prevMeasuredStateTime) { + private Long getTime(CircuitBreakerState measuredState, long measuredStateStart, AtomicLong prevMeasuredStateTime) { return state == measuredState ? prevMeasuredStateTime.get() + System.nanoTime() - measuredStateStart : prevMeasuredStateTime.get(); diff --git a/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/CdiFaultTolerance.java b/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/CdiFaultTolerance.java deleted file mode 100644 index b33ed7d2..00000000 --- a/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/CdiFaultTolerance.java +++ /dev/null @@ -1,657 +0,0 @@ -package io.smallrye.faulttolerance; - -import static io.smallrye.faulttolerance.core.Invocation.invocation; - -import java.time.Duration; -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.CompletionStage; -import java.util.concurrent.Executor; -import java.util.function.Function; -import java.util.function.Supplier; - -import io.smallrye.faulttolerance.api.CustomBackoffStrategy; -import io.smallrye.faulttolerance.api.FaultTolerance; -import io.smallrye.faulttolerance.core.FaultToleranceStrategy; -import io.smallrye.faulttolerance.core.InvocationContext; -import io.smallrye.faulttolerance.core.async.CompletionStageExecution; -import io.smallrye.faulttolerance.core.async.RememberEventLoop; -import io.smallrye.faulttolerance.core.async.types.AsyncTypes; -import io.smallrye.faulttolerance.core.async.types.AsyncTypesConversion; -import io.smallrye.faulttolerance.core.bulkhead.CompletionStageThreadPoolBulkhead; -import io.smallrye.faulttolerance.core.bulkhead.SemaphoreBulkhead; -import io.smallrye.faulttolerance.core.circuit.breaker.CircuitBreaker; -import io.smallrye.faulttolerance.core.circuit.breaker.CompletionStageCircuitBreaker; -import io.smallrye.faulttolerance.core.event.loop.EventLoop; -import io.smallrye.faulttolerance.core.fallback.CompletionStageFallback; -import io.smallrye.faulttolerance.core.fallback.Fallback; -import io.smallrye.faulttolerance.core.fallback.FallbackFunction; -import io.smallrye.faulttolerance.core.retry.BackOff; -import io.smallrye.faulttolerance.core.retry.CompletionStageRetry; -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.CompletionStageTimeout; -import io.smallrye.faulttolerance.core.timeout.Timeout; -import io.smallrye.faulttolerance.core.timeout.TimerTimeoutWatcher; -import io.smallrye.faulttolerance.core.timer.Timer; -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.SetOfThrowables; - -// this is mostly a copy of StandaloneFaultTolerance and they must be kept in sync -class CdiFaultTolerance implements FaultTolerance { - private final FaultToleranceStrategy strategy; - - CdiFaultTolerance(FaultToleranceStrategy strategy) { - this.strategy = strategy; - } - - @Override - public T call(Callable action) throws Exception { - InvocationContext ctx = new InvocationContext<>(action); - return strategy.apply(ctx); - } - - static class BuilderImpl implements Builder { - private final boolean ftEnabled; - private final Executor executor; - private final Timer timer; - private final EventLoop eventLoop; - private final CircuitBreakerMaintenanceImpl cbMaintenance; - private final boolean isAsync; - private final Class asyncType; // ignored when isAsync == false - private final Function, R> finisher; - - private String description; - private BulkheadBuilderImpl bulkheadBuilder; - private CircuitBreakerBuilderImpl circuitBreakerBuilder; - private FallbackBuilderImpl fallbackBuilder; - private RetryBuilderImpl retryBuilder; - private TimeoutBuilderImpl timeoutBuilder; - private boolean offloadToAnotherThread; - - BuilderImpl(boolean ftEnabled, Executor executor, Timer timer, EventLoop eventLoop, - CircuitBreakerMaintenanceImpl cbMaintenance, boolean isAsync, Class asyncType, - Function, R> finisher) { - this.ftEnabled = ftEnabled; - this.executor = executor; - this.timer = timer; - this.eventLoop = eventLoop; - this.cbMaintenance = cbMaintenance; - this.isAsync = isAsync; - this.asyncType = asyncType; - this.finisher = finisher; - - 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 RetryBuilder withRetry() { - return new RetryBuilderImpl<>(this); - } - - @Override - public TimeoutBuilder withTimeout() { - return new TimeoutBuilderImpl<>(this); - } - - @Override - public Builder withThreadOffload(boolean value) { - if (!isAsync) { - throw new IllegalStateException("Thread offload may only be set for asynchronous invocations"); - } - - this.offloadToAnotherThread = value; - return this; - } - - @Override - public R build() { - FaultTolerance result; - if (isAsync) { - result = new CdiFaultTolerance(buildAsyncStrategy()); - } else { - result = new CdiFaultTolerance<>(buildSyncStrategy()); - } - return finisher.apply(result); - } - - private FaultToleranceStrategy buildSyncStrategy() { - FaultToleranceStrategy result = invocation(); - - if (ftEnabled && bulkheadBuilder != null) { - result = new SemaphoreBulkhead<>(result, description, bulkheadBuilder.limit); - } - - if (ftEnabled && timeoutBuilder != null) { - result = new Timeout<>(result, description, timeoutBuilder.durationInMillis, - new TimerTimeoutWatcher(timer)); - } - - if (ftEnabled && circuitBreakerBuilder != null) { - result = new CircuitBreaker<>(result, description, - createExceptionDecision(circuitBreakerBuilder.skipOn, circuitBreakerBuilder.failOn), - circuitBreakerBuilder.delayInMillis, - circuitBreakerBuilder.requestVolumeThreshold, - circuitBreakerBuilder.failureRatio, - circuitBreakerBuilder.successThreshold, - new SystemStopwatch()); - - String cbName = circuitBreakerBuilder.name != null ? circuitBreakerBuilder.name : UUID.randomUUID().toString(); - cbMaintenance.register(cbName, (CircuitBreaker) result); - } - - if (ftEnabled && retryBuilder != null) { - Supplier backoff = prepareRetryBackoff(retryBuilder); - - result = new Retry<>(result, description, - createExceptionDecision(retryBuilder.abortOn, retryBuilder.retryOn), retryBuilder.maxRetries, - retryBuilder.maxDurationInMillis, () -> new ThreadSleepDelay(backoff.get()), - new SystemStopwatch()); - } - - // fallback is always enabled - if (fallbackBuilder != null) { - FallbackFunction fallbackFunction = ctx -> fallbackBuilder.handler.apply(ctx.failure); - result = new Fallback<>(result, description, fallbackFunction, - createExceptionDecision(fallbackBuilder.skipOn, fallbackBuilder.applyOn)); - } - - return result; - } - - private FaultToleranceStrategy buildAsyncStrategy() { - // FaultToleranceStrategy expects that the input type and output type are the same, which - // isn't true for the conversion strategies used below (even though the entire chain does - // retain the type, the conversions are only intermediate) - // that's why we use raw types here, to work around this design choice - - FaultToleranceStrategy result = invocation(); - - result = new AsyncTypesConversion.ToCompletionStage(result, AsyncTypes.get(asyncType)); - - result = buildCompletionStageChain(result); - - result = new AsyncTypesConversion.FromCompletionStage(result, AsyncTypes.get(asyncType)); - - return result; - } - - private FaultToleranceStrategy> buildCompletionStageChain( - FaultToleranceStrategy> invocation) { - FaultToleranceStrategy> result = invocation; - - // thread offload is always enabled - Executor executor = offloadToAnotherThread ? this.executor : DirectExecutor.INSTANCE; - result = new CompletionStageExecution<>(result, executor); - - if (ftEnabled && bulkheadBuilder != null) { - result = new CompletionStageThreadPoolBulkhead<>(result, description, bulkheadBuilder.limit, - bulkheadBuilder.queueSize); - } - - if (ftEnabled && timeoutBuilder != null) { - result = new CompletionStageTimeout<>(result, description, timeoutBuilder.durationInMillis, - new TimerTimeoutWatcher(timer)); - } - - if (ftEnabled && circuitBreakerBuilder != null) { - result = new CompletionStageCircuitBreaker<>(result, description, - createExceptionDecision(circuitBreakerBuilder.skipOn, circuitBreakerBuilder.failOn), - circuitBreakerBuilder.delayInMillis, - circuitBreakerBuilder.requestVolumeThreshold, - circuitBreakerBuilder.failureRatio, - circuitBreakerBuilder.successThreshold, - new SystemStopwatch()); - - String cbName = circuitBreakerBuilder.name != null ? circuitBreakerBuilder.name : UUID.randomUUID().toString(); - cbMaintenance.register(cbName, (CircuitBreaker) result); - } - - if (ftEnabled && retryBuilder != null) { - Supplier backoff = prepareRetryBackoff(retryBuilder); - - result = new CompletionStageRetry<>(result, description, - createExceptionDecision(retryBuilder.abortOn, retryBuilder.retryOn), retryBuilder.maxRetries, - retryBuilder.maxDurationInMillis, () -> new TimerDelay(backoff.get(), timer), - new SystemStopwatch()); - } - - // fallback is always enabled - if (fallbackBuilder != null) { - FallbackFunction> fallbackFunction = ctx -> AsyncTypes.toCompletionStageIfRequired( - fallbackBuilder.handler.apply(ctx.failure), asyncType); - result = new CompletionStageFallback<>(result, description, fallbackFunction, - createExceptionDecision(fallbackBuilder.skipOn, fallbackBuilder.applyOn)); - } - - // thread offload is always enabled - if (!offloadToAnotherThread) { - result = new RememberEventLoop<>(result, eventLoop); - } - - return result; - } - - private static long getTimeInMs(long time, ChronoUnit unit) { - return Duration.of(time, unit).toMillis(); - } - - private static ExceptionDecision createExceptionDecision(Collection> consideredExpected, - Collection> consideredFailure) { - return new ExceptionDecision(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; - - 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) { - if (!parent.isAsync) { - throw new IllegalStateException("Bulkhead queue size may only be set for asynchronous invocations"); - } - - this.queueSize = Preconditions.check(value, value >= 1, "Queue size must be >= 1"); - 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 long delayInMillis = 5000; - private int requestVolumeThreshold = 20; - private double failureRatio = 0.5; - private int successThreshold = 1; - - private String name; // unnamed by default - - CircuitBreakerBuilderImpl(BuilderImpl parent) { - this.parent = parent; - } - - @Override - public CircuitBreakerBuilder failOn(Collection> value) { - this.failOn = Preconditions.checkNotNull(value, "Exceptions considered failure must be set"); - return this; - } - - @Override - public CircuitBreakerBuilder skipOn(Collection> value) { - this.skipOn = Preconditions.checkNotNull(value, "Exceptions considered success 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 = getTimeInMs(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 Builder done() { - 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(); - - 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"); - return this; - } - - @Override - public FallbackBuilder skipOn(Collection> value) { - this.skipOn = Preconditions.checkNotNull(value, "Exceptions to skip fallback on must be set"); - return this; - } - - @Override - public Builder done() { - Preconditions.checkNotNull(handler, "Fallback handler must be set"); - - parent.fallbackBuilder = 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 ExponentialBackoffBuilderImpl exponentialBackoffBuilder; - private FibonacciBackoffBuilderImpl fibonacciBackoffBuilder; - private CustomBackoffBuilderImpl customBackoffBuilder; - - 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 = getTimeInMs(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 = getTimeInMs(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 = getTimeInMs(value, unit); - return this; - } - - @Override - public RetryBuilder retryOn(Collection> value) { - this.retryOn = Preconditions.checkNotNull(value, "Exceptions to retry on must be set"); - return this; - } - - @Override - public RetryBuilder abortOn(Collection> value) { - this.abortOn = Preconditions.checkNotNull(value, "Exceptions to abort retrying on 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 Builder done() { - 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 = getTimeInMs(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 = getTimeInMs(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; - - 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 = getTimeInMs(value, unit); - return this; - } - - @Override - public Builder done() { - parent.timeoutBuilder = this; - return parent; - } - } - } -} diff --git a/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/CdiFaultToleranceSpi.java b/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/CdiFaultToleranceSpi.java index 5987901e..b22b1b72 100644 --- a/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/CdiFaultToleranceSpi.java +++ b/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/CdiFaultToleranceSpi.java @@ -12,6 +12,7 @@ import io.smallrye.faulttolerance.api.CircuitBreakerMaintenance; import io.smallrye.faulttolerance.api.FaultTolerance; import io.smallrye.faulttolerance.api.FaultToleranceSpi; +import io.smallrye.faulttolerance.core.apiimpl.FaultToleranceImpl; import io.smallrye.faulttolerance.core.event.loop.EventLoop; import io.smallrye.faulttolerance.core.timer.Timer; @@ -64,14 +65,14 @@ public int priority() { @Override public FaultTolerance.Builder newBuilder(Function, R> finisher) { Dependencies deps = getDependencies(); - return new CdiFaultTolerance.BuilderImpl<>(deps.ftEnabled, deps.asyncExecutor(), deps.timer(), deps.eventLoop(), + return new FaultToleranceImpl.BuilderImpl<>(deps.ftEnabled, deps.asyncExecutor(), deps.timer(), deps.eventLoop(), deps.cbMaintenance, false, null, finisher); } @Override public FaultTolerance.Builder newAsyncBuilder(Class asyncType, Function, R> finisher) { Dependencies deps = getDependencies(); - return new CdiFaultTolerance.BuilderImpl<>(deps.ftEnabled, deps.asyncExecutor(), deps.timer(), deps.eventLoop(), + return new FaultToleranceImpl.BuilderImpl<>(deps.ftEnabled, deps.asyncExecutor(), deps.timer(), deps.eventLoop(), deps.cbMaintenance, true, asyncType, finisher); } diff --git a/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/CircuitBreakerMaintenanceImpl.java b/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/CircuitBreakerMaintenanceImpl.java index c56d0283..a27e7451 100644 --- a/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/CircuitBreakerMaintenanceImpl.java +++ b/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/CircuitBreakerMaintenanceImpl.java @@ -6,12 +6,12 @@ import javax.inject.Inject; import javax.inject.Singleton; -import io.smallrye.faulttolerance.api.CircuitBreakerMaintenance; import io.smallrye.faulttolerance.api.CircuitBreakerState; +import io.smallrye.faulttolerance.core.apiimpl.CircuitBreakerMaintenanceInternal; import io.smallrye.faulttolerance.core.circuit.breaker.CircuitBreaker; @Singleton -public class CircuitBreakerMaintenanceImpl implements CircuitBreakerMaintenance { +public class CircuitBreakerMaintenanceImpl implements CircuitBreakerMaintenanceInternal { private final ConcurrentMap> registry = new ConcurrentHashMap<>(); private final ExistingCircuitBreakerNames existingCircuitBreakerNames; @@ -21,7 +21,8 @@ public CircuitBreakerMaintenanceImpl(ExistingCircuitBreakerNames existingCircuit this.existingCircuitBreakerNames = existingCircuitBreakerNames; } - void register(String circuitBreakerName, CircuitBreaker circuitBreaker) { + @Override + public void register(String circuitBreakerName, CircuitBreaker circuitBreaker) { registry.putIfAbsent(circuitBreakerName, circuitBreaker); } diff --git a/implementation/standalone/pom.xml b/implementation/standalone/pom.xml index 0c35739a..284ac947 100644 --- a/implementation/standalone/pom.xml +++ b/implementation/standalone/pom.xml @@ -49,6 +49,11 @@ assertj-core test + + org.awaitility + awaitility + test + diff --git a/implementation/standalone/src/main/java/io/smallrye/faulttolerance/standalone/StandaloneCircuitBreakerMaintenance.java b/implementation/standalone/src/main/java/io/smallrye/faulttolerance/standalone/StandaloneCircuitBreakerMaintenance.java index 01cd3b6c..8e0ab451 100644 --- a/implementation/standalone/src/main/java/io/smallrye/faulttolerance/standalone/StandaloneCircuitBreakerMaintenance.java +++ b/implementation/standalone/src/main/java/io/smallrye/faulttolerance/standalone/StandaloneCircuitBreakerMaintenance.java @@ -3,14 +3,15 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; -import io.smallrye.faulttolerance.api.CircuitBreakerMaintenance; import io.smallrye.faulttolerance.api.CircuitBreakerState; +import io.smallrye.faulttolerance.core.apiimpl.CircuitBreakerMaintenanceInternal; import io.smallrye.faulttolerance.core.circuit.breaker.CircuitBreaker; -class StandaloneCircuitBreakerMaintenance implements CircuitBreakerMaintenance { +class StandaloneCircuitBreakerMaintenance implements CircuitBreakerMaintenanceInternal { private final ConcurrentMap> registry = new ConcurrentHashMap<>(); - void register(String circuitBreakerName, CircuitBreaker circuitBreaker) { + @Override + public void register(String circuitBreakerName, CircuitBreaker circuitBreaker) { registry.putIfAbsent(circuitBreakerName, circuitBreaker); } diff --git a/implementation/standalone/src/main/java/io/smallrye/faulttolerance/standalone/StandaloneFaultToleranceSpi.java b/implementation/standalone/src/main/java/io/smallrye/faulttolerance/standalone/StandaloneFaultToleranceSpi.java index b6676f19..3ec0e7d6 100644 --- a/implementation/standalone/src/main/java/io/smallrye/faulttolerance/standalone/StandaloneFaultToleranceSpi.java +++ b/implementation/standalone/src/main/java/io/smallrye/faulttolerance/standalone/StandaloneFaultToleranceSpi.java @@ -7,6 +7,7 @@ import io.smallrye.faulttolerance.api.CircuitBreakerMaintenance; import io.smallrye.faulttolerance.api.FaultTolerance; import io.smallrye.faulttolerance.api.FaultToleranceSpi; +import io.smallrye.faulttolerance.core.apiimpl.FaultToleranceImpl; import io.smallrye.faulttolerance.core.event.loop.EventLoop; import io.smallrye.faulttolerance.core.timer.Timer; @@ -39,14 +40,14 @@ public int priority() { @Override public FaultTolerance.Builder newBuilder(Function, R> finisher) { Dependencies deps = DependenciesHolder.INSTANCE; - return new StandaloneFaultTolerance.BuilderImpl<>(deps.ftEnabled, deps.executor, deps.timer, deps.eventLoop, + return new FaultToleranceImpl.BuilderImpl<>(deps.ftEnabled, deps.executor, deps.timer, deps.eventLoop, deps.cbMaintenance, false, null, finisher); } @Override public FaultTolerance.Builder newAsyncBuilder(Class asyncType, Function, R> finisher) { Dependencies deps = DependenciesHolder.INSTANCE; - return new StandaloneFaultTolerance.BuilderImpl<>(deps.ftEnabled, deps.executor, deps.timer, deps.eventLoop, + return new FaultToleranceImpl.BuilderImpl<>(deps.ftEnabled, deps.executor, deps.timer, deps.eventLoop, deps.cbMaintenance, true, asyncType, finisher); } 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 new file mode 100644 index 00000000..82d7b620 --- /dev/null +++ b/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneBulkheadAsyncEventsTest.java @@ -0,0 +1,71 @@ +package io.smallrye.faulttolerance.standalone.test; + +import static io.smallrye.faulttolerance.core.util.CompletionStages.completedStage; +import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.await; + +import java.util.concurrent.CompletionStage; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +import org.eclipse.microprofile.faulttolerance.exceptions.BulkheadException; +import org.junit.jupiter.api.Test; + +import io.smallrye.faulttolerance.api.FaultTolerance; +import io.smallrye.faulttolerance.core.util.party.Party; + +public class StandaloneBulkheadAsyncEventsTest { + @Test + public void asyncBulkheadEvents() throws Exception { + AtomicInteger acceptedCounter = new AtomicInteger(); + AtomicInteger rejectedCounter = new AtomicInteger(); + AtomicInteger finishedCounter = new AtomicInteger(); + + FaultTolerance> guarded = FaultTolerance. createAsync() + .withBulkhead() + .limit(5) + .queueSize(5) + .onAccepted(acceptedCounter::incrementAndGet) + .onRejected(rejectedCounter::incrementAndGet) + .onFinished(finishedCounter::incrementAndGet) + .done() + .withFallback().handler(this::fallback).applyOn(BulkheadException.class).done() + .withThreadOffload(true) + .build(); + + Party party = Party.create(5); + + for (int i = 0; i < 10; i++) { + guarded.call(() -> { + party.participant().attend(); + return completedStage("ignored"); + }); + } + + party.organizer().waitForAll(); + + assertThat(acceptedCounter).hasValue(10); + assertThat(rejectedCounter).hasValue(0); + assertThat(finishedCounter).hasValue(0); + + for (int i = 0; i < 3; i++) { + assertThat(guarded.call(() -> completedStage("value"))) + .succeedsWithin(10, TimeUnit.SECONDS) + .isEqualTo("fallback"); + } + + assertThat(acceptedCounter).hasValue(10); + assertThat(rejectedCounter).hasValue(3); + assertThat(finishedCounter).hasValue(0); + + party.organizer().disband(); + + await().untilAsserted(() -> { + assertThat(finishedCounter).hasValue(10); + }); + } + + public CompletionStage fallback() { + return completedStage("fallback"); + } +} 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 new file mode 100644 index 00000000..ab579e3a --- /dev/null +++ b/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneBulkheadEventsTest.java @@ -0,0 +1,84 @@ +package io.smallrye.faulttolerance.standalone.test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.await; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +import org.eclipse.microprofile.faulttolerance.exceptions.BulkheadException; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import io.smallrye.faulttolerance.api.FaultTolerance; +import io.smallrye.faulttolerance.core.util.party.Party; + +public class StandaloneBulkheadEventsTest { + private ExecutorService executor; + + @BeforeEach + public void setUp() { + executor = Executors.newFixedThreadPool(5); + } + + @AfterEach + public void tearDown() throws InterruptedException { + executor.shutdownNow(); + executor.awaitTermination(1, TimeUnit.SECONDS); + } + + @Test + public void bulkheadEvents() throws Exception { + AtomicInteger acceptedCounter = new AtomicInteger(); + AtomicInteger rejectedCounter = new AtomicInteger(); + AtomicInteger finishedCounter = new AtomicInteger(); + + FaultTolerance guarded = FaultTolerance. create() + .withBulkhead() + .limit(5) + .onAccepted(acceptedCounter::incrementAndGet) + .onRejected(rejectedCounter::incrementAndGet) + .onFinished(finishedCounter::incrementAndGet) + .done() + .withFallback().handler(this::fallback).applyOn(BulkheadException.class).done() + .build(); + + Party party = Party.create(5); + + for (int i = 0; i < 5; i++) { + executor.submit(() -> { + return guarded.call(() -> { + party.participant().attend(); + return "ignored"; + }); + }); + } + + party.organizer().waitForAll(); + + assertThat(acceptedCounter).hasValue(5); + assertThat(rejectedCounter).hasValue(0); + assertThat(finishedCounter).hasValue(0); + + for (int i = 0; i < 3; i++) { + assertThat(guarded.call(() -> "value")).isEqualTo("fallback"); + } + + assertThat(acceptedCounter).hasValue(5); + assertThat(rejectedCounter).hasValue(3); + assertThat(finishedCounter).hasValue(0); + + party.organizer().disband(); + + await().untilAsserted(() -> { + assertThat(finishedCounter).hasValue(5); + }); + } + + public String fallback() { + return "fallback"; + } +} 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 new file mode 100644 index 00000000..72741fcc --- /dev/null +++ b/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneCircuitBreakerAsyncEventsTest.java @@ -0,0 +1,90 @@ +package io.smallrye.faulttolerance.standalone.test; + +import static io.smallrye.faulttolerance.core.util.CompletionStages.completedStage; +import static io.smallrye.faulttolerance.core.util.CompletionStages.failedStage; +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.concurrent.CompletionStage; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Supplier; + +import org.eclipse.microprofile.faulttolerance.exceptions.CircuitBreakerOpenException; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import io.smallrye.faulttolerance.api.CircuitBreakerState; +import io.smallrye.faulttolerance.api.FaultTolerance; +import io.smallrye.faulttolerance.core.util.TestException; + +public class StandaloneCircuitBreakerAsyncEventsTest { + private boolean actionShouldFail; + + @BeforeEach + public void setUp() { + FaultTolerance.circuitBreakerMaintenance().resetAll(); + } + + @Test + public void asyncCircuitBreakerEvents() throws Exception { + AtomicInteger successCounter = new AtomicInteger(); + AtomicInteger failureCounter = new AtomicInteger(); + AtomicInteger preventedCounter = new AtomicInteger(); + AtomicReference changedState = new AtomicReference<>(); + + Supplier> guarded = FaultTolerance.createAsyncSupplier(this::action) + .withCircuitBreaker() + .requestVolumeThreshold(4) + .onSuccess(successCounter::incrementAndGet) + .onFailure(failureCounter::incrementAndGet) + .onPrevented(preventedCounter::incrementAndGet) + .onStateChange(changedState::set) + .done() + .withFallback().handler(this::fallback).applyOn(CircuitBreakerOpenException.class).done() + .build(); + + actionShouldFail = false; + for (int i = 0; i < 2; i++) { + assertThat(guarded.get()) + .succeedsWithin(10, TimeUnit.SECONDS) + .isEqualTo("value"); + } + assertThat(successCounter).hasValue(2); + assertThat(failureCounter).hasValue(0); + assertThat(preventedCounter).hasValue(0); + assertThat(changedState).hasValue(null); + + actionShouldFail = true; + for (int i = 0; i < 2; i++) { + assertThat(guarded.get()) + .failsWithin(10, TimeUnit.SECONDS) + .withThrowableOfType(ExecutionException.class) // caused by AssertJ calling future.get() + .withCauseExactlyInstanceOf(TestException.class); + } + assertThat(successCounter).hasValue(2); + assertThat(failureCounter).hasValue(2); + assertThat(preventedCounter).hasValue(0); + assertThat(changedState).hasValue(CircuitBreakerState.OPEN); + + assertThat(guarded.get()) + .succeedsWithin(10, TimeUnit.SECONDS) + .isEqualTo("fallback"); + assertThat(successCounter).hasValue(2); + assertThat(failureCounter).hasValue(2); + assertThat(preventedCounter).hasValue(1); + } + + public CompletionStage action() { + if (actionShouldFail) { + return failedStage(new TestException()); + } else { + return completedStage("value"); + } + } + + public CompletionStage fallback() { + return completedStage("fallback"); + } +} 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 new file mode 100644 index 00000000..354e0640 --- /dev/null +++ b/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneCircuitBreakerEventsTest.java @@ -0,0 +1,79 @@ +package io.smallrye.faulttolerance.standalone.test; + +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.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; + +import org.eclipse.microprofile.faulttolerance.exceptions.CircuitBreakerOpenException; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import io.smallrye.faulttolerance.api.CircuitBreakerState; +import io.smallrye.faulttolerance.api.FaultTolerance; +import io.smallrye.faulttolerance.core.util.TestException; + +public class StandaloneCircuitBreakerEventsTest { + private boolean actionShouldFail; + + @BeforeEach + public void setUp() { + FaultTolerance.circuitBreakerMaintenance().resetAll(); + } + + @Test + public void circuitBreakerEvents() throws Exception { + AtomicInteger successCounter = new AtomicInteger(); + AtomicInteger failureCounter = new AtomicInteger(); + AtomicInteger preventedCounter = new AtomicInteger(); + AtomicReference changedState = new AtomicReference<>(); + + Callable guarded = FaultTolerance.createCallable(this::action) + .withCircuitBreaker() + .requestVolumeThreshold(4) + .onSuccess(successCounter::incrementAndGet) + .onFailure(failureCounter::incrementAndGet) + .onPrevented(preventedCounter::incrementAndGet) + .onStateChange(changedState::set) + .done() + .withFallback().handler(this::fallback).applyOn(CircuitBreakerOpenException.class).done() + .build(); + + actionShouldFail = false; + for (int i = 0; i < 2; i++) { + assertThat(guarded.call()).isEqualTo("value"); + } + assertThat(successCounter).hasValue(2); + assertThat(failureCounter).hasValue(0); + assertThat(preventedCounter).hasValue(0); + assertThat(changedState).hasValue(null); + + actionShouldFail = true; + for (int i = 0; i < 2; i++) { + assertThatCode(guarded::call).isExactlyInstanceOf(TestException.class); + } + assertThat(successCounter).hasValue(2); + assertThat(failureCounter).hasValue(2); + assertThat(preventedCounter).hasValue(0); + assertThat(changedState).hasValue(CircuitBreakerState.OPEN); + + assertThat(guarded.call()).isEqualTo("fallback"); + assertThat(successCounter).hasValue(2); + assertThat(failureCounter).hasValue(2); + assertThat(preventedCounter).hasValue(1); + } + + public String action() throws TestException { + if (actionShouldFail) { + throw new TestException(); + } else { + return "value"; + } + } + + public String fallback() { + return "fallback"; + } +} 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 new file mode 100644 index 00000000..7ec589ab --- /dev/null +++ b/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneRetryAsyncEventsTest.java @@ -0,0 +1,65 @@ +package io.smallrye.faulttolerance.standalone.test; + +import static io.smallrye.faulttolerance.core.util.CompletionStages.completedStage; +import static io.smallrye.faulttolerance.core.util.CompletionStages.failedStage; +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.concurrent.CompletionStage; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Supplier; + +import org.junit.jupiter.api.Test; + +import io.smallrye.faulttolerance.api.FaultTolerance; +import io.smallrye.faulttolerance.core.util.TestException; + +public class StandaloneRetryAsyncEventsTest { + private int failTimes; + + @Test + public void asyncRetry() { + AtomicInteger retryCounter = new AtomicInteger(); + AtomicInteger successCounter = new AtomicInteger(); + AtomicInteger failureCounter = new AtomicInteger(); + + Supplier> guarded = FaultTolerance.createAsyncSupplier(this::action) + .withRetry() + .maxRetries(3) + .onRetry(retryCounter::incrementAndGet) + .onSuccess(successCounter::incrementAndGet) + .onFailure(failureCounter::incrementAndGet) + .done() + .withFallback().handler(this::fallback).applyOn(TestException.class).done() + .build(); + + failTimes = 10; + assertThat(guarded.get()) + .succeedsWithin(10, TimeUnit.SECONDS) + .isEqualTo("fallback"); + assertThat(retryCounter).hasValue(3); + assertThat(successCounter).hasValue(0); + assertThat(failureCounter).hasValue(1); + + failTimes = 2; + assertThat(guarded.get()) + .succeedsWithin(10, TimeUnit.SECONDS) + .isEqualTo("value"); + assertThat(retryCounter).hasValue(5); + assertThat(successCounter).hasValue(1); + assertThat(failureCounter).hasValue(1); + } + + public CompletionStage action() { + if (failTimes > 0) { + failTimes--; + return failedStage(new TestException()); + } + + return completedStage("value"); + } + + public CompletionStage fallback() { + return completedStage("fallback"); + } +} 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 new file mode 100644 index 00000000..26a4e4b0 --- /dev/null +++ b/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneRetryEventsTest.java @@ -0,0 +1,57 @@ +package io.smallrye.faulttolerance.standalone.test; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.concurrent.Callable; +import java.util.concurrent.atomic.AtomicInteger; + +import org.junit.jupiter.api.Test; + +import io.smallrye.faulttolerance.api.FaultTolerance; +import io.smallrye.faulttolerance.core.util.TestException; + +public class StandaloneRetryEventsTest { + private int failTimes; + + @Test + public void retryEvents() throws Exception { + AtomicInteger retryCounter = new AtomicInteger(); + AtomicInteger successCounter = new AtomicInteger(); + AtomicInteger failureCounter = new AtomicInteger(); + + Callable guarded = FaultTolerance.createCallable(this::action) + .withRetry() + .maxRetries(3) + .onRetry(retryCounter::incrementAndGet) + .onSuccess(successCounter::incrementAndGet) + .onFailure(failureCounter::incrementAndGet) + .done() + .withFallback().applyOn(TestException.class).handler(this::fallback).done() + .build(); + + failTimes = 10; + assertThat(guarded.call()).isEqualTo("fallback"); + assertThat(retryCounter).hasValue(3); + assertThat(successCounter).hasValue(0); + assertThat(failureCounter).hasValue(1); + + failTimes = 2; + assertThat(guarded.call()).isEqualTo("value"); + assertThat(retryCounter).hasValue(5); + assertThat(successCounter).hasValue(1); + assertThat(failureCounter).hasValue(1); + } + + public String action() throws TestException { + if (failTimes > 0) { + failTimes--; + throw new TestException(); + } + + return "value"; + } + + public String fallback() { + return "fallback"; + } +} 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 new file mode 100644 index 00000000..8e4afdfa --- /dev/null +++ b/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneTimeoutAsyncEventsTest.java @@ -0,0 +1,60 @@ +package io.smallrye.faulttolerance.standalone.test; + +import static io.smallrye.faulttolerance.core.util.CompletionStages.completedStage; +import static org.assertj.core.api.Assertions.assertThat; + +import java.time.temporal.ChronoUnit; +import java.util.concurrent.Callable; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +import org.eclipse.microprofile.faulttolerance.exceptions.TimeoutException; +import org.junit.jupiter.api.Test; + +import io.smallrye.faulttolerance.api.FaultTolerance; + +public class StandaloneTimeoutAsyncEventsTest { + private volatile boolean shouldSleep; + + @Test + public void asyncTimeoutEvents() throws Exception { + AtomicInteger timeoutCounter = new AtomicInteger(); + AtomicInteger finishedCounter = new AtomicInteger(); + + Callable> guarded = FaultTolerance.createAsyncCallable(this::action) + .withTimeout() + .duration(1, ChronoUnit.SECONDS) + .onTimeout(timeoutCounter::incrementAndGet) + .onFinished(finishedCounter::incrementAndGet) + .done() + .withFallback().applyOn(TimeoutException.class).handler(this::fallback).done() + .withThreadOffload(true) // async timeout doesn't interrupt the running thread + .build(); + + shouldSleep = true; + assertThat(guarded.call()) + .succeedsWithin(5, TimeUnit.SECONDS) + .isEqualTo("fallback"); + assertThat(timeoutCounter).hasValue(1); + assertThat(finishedCounter).hasValue(0); + + shouldSleep = false; + assertThat(guarded.call()) + .succeedsWithin(5, TimeUnit.SECONDS) + .isEqualTo("value"); + assertThat(timeoutCounter).hasValue(1); + assertThat(finishedCounter).hasValue(1); + } + + public CompletionStage action() throws InterruptedException { + if (shouldSleep) { + Thread.sleep(10_000); + } + return completedStage("value"); + } + + public CompletionStage fallback() { + return completedStage("fallback"); + } +} 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 new file mode 100644 index 00000000..51d3a4ed --- /dev/null +++ b/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneTimeoutEventsTest.java @@ -0,0 +1,52 @@ +package io.smallrye.faulttolerance.standalone.test; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.time.temporal.ChronoUnit; +import java.util.concurrent.Callable; +import java.util.concurrent.atomic.AtomicInteger; + +import org.eclipse.microprofile.faulttolerance.exceptions.TimeoutException; +import org.junit.jupiter.api.Test; + +import io.smallrye.faulttolerance.api.FaultTolerance; + +public class StandaloneTimeoutEventsTest { + private boolean shouldSleep; + + @Test + public void timeoutEvents() throws Exception { + AtomicInteger timeoutCounter = new AtomicInteger(); + AtomicInteger finishedCounter = new AtomicInteger(); + + Callable guarded = FaultTolerance.createCallable(this::action) + .withTimeout() + .duration(1000, ChronoUnit.MILLIS) + .onTimeout(timeoutCounter::incrementAndGet) + .onFinished(finishedCounter::incrementAndGet) + .done() + .withFallback().applyOn(TimeoutException.class).handler(this::fallback).done() + .build(); + + shouldSleep = true; + assertThat(guarded.call()).isEqualTo("fallback"); + assertThat(timeoutCounter).hasValue(1); + assertThat(finishedCounter).hasValue(0); + + shouldSleep = false; + assertThat(guarded.call()).isEqualTo("value"); + assertThat(timeoutCounter).hasValue(1); + assertThat(finishedCounter).hasValue(1); + } + + public String action() throws InterruptedException { + if (shouldSleep) { + Thread.sleep(10_000); + } + return "value"; + } + + public String fallback() { + return "fallback"; + } +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/programmatic/CdiBulkheadAsyncEventsTest.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/programmatic/CdiBulkheadAsyncEventsTest.java new file mode 100644 index 00000000..da5e7706 --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/programmatic/CdiBulkheadAsyncEventsTest.java @@ -0,0 +1,8 @@ +package io.smallrye.faulttolerance.programmatic; + +import io.smallrye.faulttolerance.standalone.test.StandaloneBulkheadAsyncEventsTest; +import io.smallrye.faulttolerance.util.FaultToleranceBasicTest; + +@FaultToleranceBasicTest +public class CdiBulkheadAsyncEventsTest extends StandaloneBulkheadAsyncEventsTest { +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/programmatic/CdiBulkheadEventsTest.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/programmatic/CdiBulkheadEventsTest.java new file mode 100644 index 00000000..941297bd --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/programmatic/CdiBulkheadEventsTest.java @@ -0,0 +1,8 @@ +package io.smallrye.faulttolerance.programmatic; + +import io.smallrye.faulttolerance.standalone.test.StandaloneBulkheadEventsTest; +import io.smallrye.faulttolerance.util.FaultToleranceBasicTest; + +@FaultToleranceBasicTest +public class CdiBulkheadEventsTest extends StandaloneBulkheadEventsTest { +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/programmatic/CdiCircuitBreakerAsyncEventsTest.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/programmatic/CdiCircuitBreakerAsyncEventsTest.java new file mode 100644 index 00000000..f6318991 --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/programmatic/CdiCircuitBreakerAsyncEventsTest.java @@ -0,0 +1,8 @@ +package io.smallrye.faulttolerance.programmatic; + +import io.smallrye.faulttolerance.standalone.test.StandaloneCircuitBreakerAsyncEventsTest; +import io.smallrye.faulttolerance.util.FaultToleranceBasicTest; + +@FaultToleranceBasicTest +public class CdiCircuitBreakerAsyncEventsTest extends StandaloneCircuitBreakerAsyncEventsTest { +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/programmatic/CdiCircuitBreakerEventsTest.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/programmatic/CdiCircuitBreakerEventsTest.java new file mode 100644 index 00000000..54d6016a --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/programmatic/CdiCircuitBreakerEventsTest.java @@ -0,0 +1,8 @@ +package io.smallrye.faulttolerance.programmatic; + +import io.smallrye.faulttolerance.standalone.test.StandaloneCircuitBreakerEventsTest; +import io.smallrye.faulttolerance.util.FaultToleranceBasicTest; + +@FaultToleranceBasicTest +public class CdiCircuitBreakerEventsTest extends StandaloneCircuitBreakerEventsTest { +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/programmatic/CdiRetryAsyncEventsTest.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/programmatic/CdiRetryAsyncEventsTest.java new file mode 100644 index 00000000..1353ad2d --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/programmatic/CdiRetryAsyncEventsTest.java @@ -0,0 +1,8 @@ +package io.smallrye.faulttolerance.programmatic; + +import io.smallrye.faulttolerance.standalone.test.StandaloneRetryAsyncEventsTest; +import io.smallrye.faulttolerance.util.FaultToleranceBasicTest; + +@FaultToleranceBasicTest +public class CdiRetryAsyncEventsTest extends StandaloneRetryAsyncEventsTest { +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/programmatic/CdiRetryEventsTest.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/programmatic/CdiRetryEventsTest.java new file mode 100644 index 00000000..381c5ab3 --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/programmatic/CdiRetryEventsTest.java @@ -0,0 +1,8 @@ +package io.smallrye.faulttolerance.programmatic; + +import io.smallrye.faulttolerance.standalone.test.StandaloneRetryEventsTest; +import io.smallrye.faulttolerance.util.FaultToleranceBasicTest; + +@FaultToleranceBasicTest +public class CdiRetryEventsTest extends StandaloneRetryEventsTest { +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/programmatic/CdiTimeoutAsyncEventsTest.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/programmatic/CdiTimeoutAsyncEventsTest.java new file mode 100644 index 00000000..5b8f5eca --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/programmatic/CdiTimeoutAsyncEventsTest.java @@ -0,0 +1,8 @@ +package io.smallrye.faulttolerance.programmatic; + +import io.smallrye.faulttolerance.standalone.test.StandaloneTimeoutAsyncEventsTest; +import io.smallrye.faulttolerance.util.FaultToleranceBasicTest; + +@FaultToleranceBasicTest +public class CdiTimeoutAsyncEventsTest extends StandaloneTimeoutAsyncEventsTest { +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/programmatic/CdiTimeoutEventsTest.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/programmatic/CdiTimeoutEventsTest.java new file mode 100644 index 00000000..365f10db --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/programmatic/CdiTimeoutEventsTest.java @@ -0,0 +1,8 @@ +package io.smallrye.faulttolerance.programmatic; + +import io.smallrye.faulttolerance.standalone.test.StandaloneTimeoutEventsTest; +import io.smallrye.faulttolerance.util.FaultToleranceBasicTest; + +@FaultToleranceBasicTest +public class CdiTimeoutEventsTest extends StandaloneTimeoutEventsTest { +} From b666e7b3c7833a181abc36299c28a38d9a533b77 Mon Sep 17 00:00:00 2001 From: Ladislav Thon Date: Tue, 8 Feb 2022 11:58:37 +0100 Subject: [PATCH 16/68] allow registering state change listeners on CircuitBreakerMaintenance This commit also includes: - updated documentation of `CircuitBreakerMaintenance` to account for programmatic API; - changed unnamed circuit breaker semantics in case of programmatic API, because remembering unnamed circuit breakers is an easy way to a memory leak (this is technically a breaking change, but the programmatic API has not been released yet, so it's fine); - removed duplicate implementations of `CircuitBreakerMaintenance`, as well as the `CircuitBreakerMaintenanceInternal` interface; - fix for storing `FaultTolerance` in fields of normal-scoped beans (related to eager/lazy registration of circuit breakers with the `CircuitBreakerMaintenance` registry) - test for `CircuitBreakerMaintenance` interoperability between the declarative and programmatic APIs --- .../api/CircuitBreakerMaintenance.java | 28 ++++- .../faulttolerance/api/FaultTolerance.java | 12 +- .../ROOT/pages/usage/programmatic-api.adoc | 36 ++++-- .../BasicCircuitBreakerMaintenanceImpl.java | 110 ++++++++++++++++++ .../CircuitBreakerMaintenanceInternal.java | 8 -- .../core/apiimpl/EventHandlers.java | 57 +++------ .../core/apiimpl/FaultToleranceImpl.java | 76 +++++++++--- .../faulttolerance/core/util/Callbacks.java | 35 ++++++ .../faulttolerance/core/util/Initializer.java | 30 +++++ .../core/util/CallbacksTest.java | 69 +++++++++++ .../core/util/InitializerTest.java | 21 ++++ .../CircuitBreakerMaintenanceImpl.java | 64 +--------- .../FaultToleranceInterceptor.java | 15 ++- .../StandaloneCircuitBreakerMaintenance.java | 53 --------- .../StandaloneFaultToleranceSpi.java | 3 +- ...andaloneCircuitBreakerMaintenanceTest.java | 66 +++++++++++ ...reakerMaintenanceInteroperabilityTest.java | 103 ++++++++++++++++ .../CircuitBreakerMaintenanceTest.java | 39 ++++--- .../maintenance/HelloService.java | 16 +++ .../CdiCircuitBreakerMaintenanceTest.java | 8 ++ 20 files changed, 629 insertions(+), 220 deletions(-) create mode 100644 implementation/core/src/main/java/io/smallrye/faulttolerance/core/apiimpl/BasicCircuitBreakerMaintenanceImpl.java delete mode 100644 implementation/core/src/main/java/io/smallrye/faulttolerance/core/apiimpl/CircuitBreakerMaintenanceInternal.java create mode 100644 implementation/core/src/main/java/io/smallrye/faulttolerance/core/util/Callbacks.java create mode 100644 implementation/core/src/main/java/io/smallrye/faulttolerance/core/util/Initializer.java create mode 100644 implementation/core/src/test/java/io/smallrye/faulttolerance/core/util/CallbacksTest.java create mode 100644 implementation/core/src/test/java/io/smallrye/faulttolerance/core/util/InitializerTest.java delete mode 100644 implementation/standalone/src/main/java/io/smallrye/faulttolerance/standalone/StandaloneCircuitBreakerMaintenance.java create mode 100644 implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneCircuitBreakerMaintenanceTest.java create mode 100644 testsuite/basic/src/test/java/io/smallrye/faulttolerance/circuitbreaker/maintenance/CircuitBreakerMaintenanceInteroperabilityTest.java create mode 100644 testsuite/basic/src/test/java/io/smallrye/faulttolerance/programmatic/CdiCircuitBreakerMaintenanceTest.java 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 608aa8aa..201e6298 100644 --- a/api/src/main/java/io/smallrye/faulttolerance/api/CircuitBreakerMaintenance.java +++ b/api/src/main/java/io/smallrye/faulttolerance/api/CircuitBreakerMaintenance.java @@ -15,11 +15,14 @@ */ package io.smallrye.faulttolerance.api; +import java.util.function.Consumer; + import io.smallrye.common.annotation.Experimental; /** - * Allows reading current state of circuit breakers and reseting them to the initial (closed) state. - * To access a specific circuit breaker, it must be given a name using {@link CircuitBreakerName @CircuitBreakerName}. + * Allows reading and observing current state of circuit breakers and reseting 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("...")}. */ @Experimental("first attempt at providing maintenance access to circuit breakers") public interface CircuitBreakerMaintenance { @@ -28,22 +31,37 @@ public interface CircuitBreakerMaintenance { * 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 {@code @CircuitBreakerName}. + * 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("...")}. */ CircuitBreakerState currentState(String name); + /** + * Registers a {@code callback} to be called when the circuit breaker with given {@code name} + * 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("...")}. + *

+ * The callback must be fast and non-blocking and must not throw an exception. + */ + void onStateChange(String name, Consumer callback); + /** * Resets the circuit breaker with given {@code name} to the initial (closed) state. *

* This method should not be used regularly, it's mainly meant for testing (to isolate individual tests) * and perhaps emergency maintenance tasks. *

- * It is an error to use a {@code name} that wasn't registered using {@code @CircuitBreakerName}. + * 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("...")}. */ void reset(String name); /** - * Resets all circuit breakers (including those without a name) in the application to the initial (closed) state. + * Resets all circuit breakers in the application to the initial (closed) state. + * This includes all named circuit breakers and unnamed circuit breakers declared using {@code @CircuitBreaker}. + * It does not include unnamed circuit breakers created using the programmatic API. *

* This method should not be used regularly, it's mainly meant for testing (to isolate individual tests) * and perhaps emergency maintenance tasks. 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 30d78485..22edf346 100644 --- a/api/src/main/java/io/smallrye/faulttolerance/api/FaultTolerance.java +++ b/api/src/main/java/io/smallrye/faulttolerance/api/FaultTolerance.java @@ -486,9 +486,15 @@ default CircuitBreakerBuilder skipOn(Class value) { CircuitBreakerBuilder successThreshold(int value); /** - * Sets a circuit breaker name. Required to use the {@link CircuitBreakerMaintenance} methods - * that accept a name parameter. Defaults to unnamed. It is an error to use the same name - * for multiple circuit breakers. + * 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 diff --git a/doc/modules/ROOT/pages/usage/programmatic-api.adoc b/doc/modules/ROOT/pages/usage/programmatic-api.adoc index 3763e8a3..9f98f7dd 100644 --- a/doc/modules/ROOT/pages/usage/programmatic-api.adoc +++ b/doc/modules/ROOT/pages/usage/programmatic-api.adoc @@ -64,11 +64,11 @@ Let's take a look at a simple example: [source,java] ---- public class MyService { - public String hello() throws Exception { - FaultTolerance guarded = FaultTolerance.create() // <1> - .withFallback().handler(() -> "fallback").done() // <2> - .build(); // <3> + private static final FaultTolerance guarded = FaultTolerance.create() // <1> + .withFallback().handler(() -> "fallback").done() // <2> + .build(); // <3> + public String hello() throws Exception { return guarded.call(() -> externalService.hello()); // <4> } } @@ -94,12 +94,12 @@ Unlike the declarative API, the programmatic API doesn't support asynchronous ac [source,java] ---- public class MyService { - public CompletionStage hello() throws Exception { - FaultTolerance> guarded = FaultTolerance.createAsync() // <1> - .withBulkhead().done() // <2> - .withThreadOffload(true) // <3> - .build(); + private static final FaultTolerance> guarded = FaultTolerance.createAsync() // <1> + .withBulkhead().done() // <2> + .withThreadOffload(true) // <3> + .build(); + public CompletionStage hello() throws Exception { return guarded.call(() -> externalService.hello()); // <4> } } @@ -178,12 +178,12 @@ If that is not necessary, you can create a `Callable`, `Supplier` or `Runnable` [source, java] ---- public class MyService { - private final Callable guard = FaultTolerance.createCallable(externalService::hello) // <1> + private static final Callable guard = FaultTolerance.createCallable(() -> externalService.hello()) // <1> .withTimeout().duration(5, ChronoUnit.SECONDS).done() .build(); public String hello() throws Exception { - guard.call(); // <2> + return guard.call(); // <2> } } ---- @@ -210,6 +210,20 @@ If you use the `adapt*` methods, the resulting `Callable`, `Supplier` or `Runnab 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. +==== 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. +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. +For this to work, all unnamed circuit breakers have to be remembered. +This is safe in case of the declarative, annotation-based API, because the number of such declared circuit breakers is fixed. +At the same time, this would _not_ be safe to do for all unnamed circuit breakers created using the programmatic API, as their number is potentially unbounded. +(In other words, remembering all unnamed circuit breakers created using the programmatic API would easily lead to a memory leak.) + +Therefore, all circuit breakers created using the programmatic API must be given a name when `CircuitBreakerMaintenance` is supposed to affect them. +Note that duplicate names are not permitted and lead to an error, so lifecycle of the circuit breaker must be carefully considered. + === Summary of `FaultTolerance` Methods There's a number of static `create*` methods on the `FaultTolerance` interface. diff --git a/implementation/core/src/main/java/io/smallrye/faulttolerance/core/apiimpl/BasicCircuitBreakerMaintenanceImpl.java b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/apiimpl/BasicCircuitBreakerMaintenanceImpl.java new file mode 100644 index 00000000..cde93cee --- /dev/null +++ b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/apiimpl/BasicCircuitBreakerMaintenanceImpl.java @@ -0,0 +1,110 @@ +package io.smallrye.faulttolerance.core.apiimpl; + +import java.util.Collection; +import java.util.Collections; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.function.Consumer; +import java.util.function.Predicate; + +import io.smallrye.faulttolerance.api.CircuitBreakerMaintenance; +import io.smallrye.faulttolerance.api.CircuitBreakerState; +import io.smallrye.faulttolerance.core.circuit.breaker.CircuitBreaker; +import io.smallrye.faulttolerance.core.circuit.breaker.CircuitBreakerEvents; +import io.smallrye.faulttolerance.core.util.Callbacks; + +public class BasicCircuitBreakerMaintenanceImpl implements CircuitBreakerMaintenance { + private final Set knownNames = Collections.newSetFromMap(new ConcurrentHashMap<>()); + private final ConcurrentMap> registry = new ConcurrentHashMap<>(); + private final ConcurrentMap>> stateChangeCallbacks = new ConcurrentHashMap<>(); + + private final Predicate circuitBreakerExists; + + public BasicCircuitBreakerMaintenanceImpl() { + this.circuitBreakerExists = knownNames::contains; + } + + protected BasicCircuitBreakerMaintenanceImpl(Predicate additionalCircuitBreakerExists) { + this.circuitBreakerExists = name -> additionalCircuitBreakerExists.test(name) || knownNames.contains(name); + } + + public void registerName(String circuitBreakerName) { + knownNames.add(circuitBreakerName); + } + + public void register(String circuitBreakerName, CircuitBreaker circuitBreaker) { + knownNames.add(circuitBreakerName); // should not be necessary, just in case + CircuitBreaker previous = registry.putIfAbsent(circuitBreakerName, circuitBreaker); + if (previous != null) { + throw new IllegalStateException("Circuit breaker already exists: " + circuitBreakerName); + } + } + + @Override + public CircuitBreakerState currentState(String name) { + if (!circuitBreakerExists.test(name)) { + throw new IllegalArgumentException("Circuit breaker '" + name + "' doesn't exist"); + } + + CircuitBreaker circuitBreaker = registry.get(name); + if (circuitBreaker == null) { + // if the circuit breaker wasn't instantiated yet, it's "closed" by definition + return CircuitBreakerState.CLOSED; + } + + int currentState = circuitBreaker.currentState(); + switch (currentState) { + case CircuitBreaker.STATE_CLOSED: + return CircuitBreakerState.CLOSED; + case CircuitBreaker.STATE_OPEN: + return CircuitBreakerState.OPEN; + case CircuitBreaker.STATE_HALF_OPEN: + return CircuitBreakerState.HALF_OPEN; + default: + throw new IllegalStateException("Unknown circuit breaker state " + currentState); + } + } + + @Override + public void onStateChange(String name, Consumer callback) { + if (!circuitBreakerExists.test(name)) { + throw new IllegalArgumentException("Circuit breaker '" + name + "' doesn't exist"); + } + + stateChangeCallbacks.computeIfAbsent(name, ignored -> new CopyOnWriteArrayList<>()) + .add(Callbacks.wrap(callback)); + } + + public Consumer stateTransitionEventHandler(String name) { + return stateTransition -> { + CircuitBreakerState targetState = stateTransition.targetState; + Collection> callbacks = stateChangeCallbacks.get(name); + if (callbacks != null) { + for (Consumer callback : callbacks) { + callback.accept(targetState); + } + } + }; + } + + @Override + public void reset(String name) { + if (!circuitBreakerExists.test(name)) { + throw new IllegalArgumentException("Circuit breaker '" + name + "' doesn't exist"); + } + + CircuitBreaker circuitBreaker = registry.get(name); + if (circuitBreaker != null) { + // if the circuit breaker wasn't instantiated yet, "resetting it" is by definition a noop + circuitBreaker.reset(); + } + } + + @Override + public void resetAll() { + // circuit breakers that weren't instantiated yet don't have to be reset + registry.values().forEach(CircuitBreaker::reset); + } +} diff --git a/implementation/core/src/main/java/io/smallrye/faulttolerance/core/apiimpl/CircuitBreakerMaintenanceInternal.java b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/apiimpl/CircuitBreakerMaintenanceInternal.java deleted file mode 100644 index 2265c14c..00000000 --- a/implementation/core/src/main/java/io/smallrye/faulttolerance/core/apiimpl/CircuitBreakerMaintenanceInternal.java +++ /dev/null @@ -1,8 +0,0 @@ -package io.smallrye.faulttolerance.core.apiimpl; - -import io.smallrye.faulttolerance.api.CircuitBreakerMaintenance; -import io.smallrye.faulttolerance.core.circuit.breaker.CircuitBreaker; - -public interface CircuitBreakerMaintenanceInternal extends CircuitBreakerMaintenance { - void register(String circuitBreakerName, CircuitBreaker circuitBreaker); -} diff --git a/implementation/core/src/main/java/io/smallrye/faulttolerance/core/apiimpl/EventHandlers.java b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/apiimpl/EventHandlers.java index 47a0411a..324473fb 100644 --- a/implementation/core/src/main/java/io/smallrye/faulttolerance/core/apiimpl/EventHandlers.java +++ b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/apiimpl/EventHandlers.java @@ -8,12 +8,14 @@ import io.smallrye.faulttolerance.core.circuit.breaker.CircuitBreakerEvents; import io.smallrye.faulttolerance.core.retry.RetryEvents; import io.smallrye.faulttolerance.core.timeout.TimeoutEvents; +import io.smallrye.faulttolerance.core.util.Callbacks; final class EventHandlers { private final Runnable bulkheadOnAccepted; private final Runnable bulkheadOnRejected; private final Runnable bulkheadOnFinished; + private final Consumer cbMaintenanceEventHandler; private final Consumer circuitBreakerOnStateChange; private final Runnable circuitBreakerOnSuccess; private final Runnable circuitBreakerOnFailure; @@ -27,47 +29,23 @@ final class EventHandlers { private final Runnable timeoutOnFinished; EventHandlers(Runnable bulkheadOnAccepted, Runnable bulkheadOnRejected, Runnable bulkheadOnFinished, + Consumer cbMaintenanceEventHandler, Consumer circuitBreakerOnStateChange, Runnable circuitBreakerOnSuccess, Runnable circuitBreakerOnFailure, Runnable circuitBreakerOnPrevented, Runnable retryOnRetry, Runnable retryOnSuccess, Runnable retryOnFailure, Runnable timeoutOnTimeout, Runnable timeoutOnFinished) { - this.bulkheadOnAccepted = wrap(bulkheadOnAccepted); - this.bulkheadOnRejected = wrap(bulkheadOnRejected); - this.bulkheadOnFinished = wrap(bulkheadOnFinished); - this.circuitBreakerOnStateChange = wrap(circuitBreakerOnStateChange); - this.circuitBreakerOnSuccess = wrap(circuitBreakerOnSuccess); - this.circuitBreakerOnFailure = wrap(circuitBreakerOnFailure); - this.circuitBreakerOnPrevented = wrap(circuitBreakerOnPrevented); - this.retryOnRetry = wrap(retryOnRetry); - this.retryOnSuccess = wrap(retryOnSuccess); - this.retryOnFailure = wrap(retryOnFailure); - this.timeoutOnTimeout = wrap(timeoutOnTimeout); - this.timeoutOnFinished = wrap(timeoutOnFinished); - } - - private static Consumer wrap(Consumer callback) { - if (callback == null) { - return null; - } - - return value -> { - try { - callback.accept(value); - } catch (Exception ignored) { - } - }; - } - - private static Runnable wrap(Runnable callback) { - if (callback == null) { - return null; - } - - return () -> { - try { - callback.run(); - } catch (Exception ignored) { - } - }; + this.bulkheadOnAccepted = Callbacks.wrap(bulkheadOnAccepted); + this.bulkheadOnRejected = Callbacks.wrap(bulkheadOnRejected); + this.bulkheadOnFinished = Callbacks.wrap(bulkheadOnFinished); + this.cbMaintenanceEventHandler = Callbacks.wrap(cbMaintenanceEventHandler); + this.circuitBreakerOnStateChange = Callbacks.wrap(circuitBreakerOnStateChange); + this.circuitBreakerOnSuccess = Callbacks.wrap(circuitBreakerOnSuccess); + this.circuitBreakerOnFailure = Callbacks.wrap(circuitBreakerOnFailure); + this.circuitBreakerOnPrevented = Callbacks.wrap(circuitBreakerOnPrevented); + this.retryOnRetry = Callbacks.wrap(retryOnRetry); + this.retryOnSuccess = Callbacks.wrap(retryOnSuccess); + this.retryOnFailure = Callbacks.wrap(retryOnFailure); + this.timeoutOnTimeout = Callbacks.wrap(timeoutOnTimeout); + this.timeoutOnFinished = Callbacks.wrap(timeoutOnFinished); } void register(InvocationContext ctx) { @@ -90,6 +68,9 @@ void register(InvocationContext ctx) { }); } + if (cbMaintenanceEventHandler != null) { + ctx.registerEventHandler(CircuitBreakerEvents.StateTransition.class, cbMaintenanceEventHandler); + } if (circuitBreakerOnStateChange != null) { ctx.registerEventHandler(CircuitBreakerEvents.StateTransition.class, event -> { CircuitBreakerState targetState = event.targetState; 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 d83b6c25..6dcf35bb 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 @@ -4,8 +4,10 @@ import java.time.Duration; import java.time.temporal.ChronoUnit; +import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.List; import java.util.UUID; import java.util.concurrent.Callable; import java.util.concurrent.CompletionStage; @@ -26,6 +28,7 @@ import io.smallrye.faulttolerance.core.bulkhead.CompletionStageThreadPoolBulkhead; import io.smallrye.faulttolerance.core.bulkhead.SemaphoreBulkhead; import io.smallrye.faulttolerance.core.circuit.breaker.CircuitBreaker; +import io.smallrye.faulttolerance.core.circuit.breaker.CircuitBreakerEvents; import io.smallrye.faulttolerance.core.circuit.breaker.CompletionStageCircuitBreaker; import io.smallrye.faulttolerance.core.event.loop.EventLoop; import io.smallrye.faulttolerance.core.fallback.CompletionStageFallback; @@ -49,20 +52,43 @@ import io.smallrye.faulttolerance.core.timer.Timer; import io.smallrye.faulttolerance.core.util.DirectExecutor; import io.smallrye.faulttolerance.core.util.ExceptionDecision; +import io.smallrye.faulttolerance.core.util.Initializer; import io.smallrye.faulttolerance.core.util.Preconditions; import io.smallrye.faulttolerance.core.util.SetOfThrowables; public final class FaultToleranceImpl implements FaultTolerance { private final FaultToleranceStrategy strategy; private final EventHandlers eventHandlers; - - FaultToleranceImpl(FaultToleranceStrategy strategy, EventHandlers eventHandlers) { + private final Initializer initializer; + + // 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 `FaultTolerance` instance. A single name may be registered + // multiple times. + // 2. The circuit breaker instance is registered lazily, during `FaultToleranceImpl.call()`, so that + // a `FaultTolerance` 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 `FaultTolerance` + // 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 `FaultTolerance`, + // but that instance is never used. The useful `FaultTolerance` instance is held by the actual bean instance, + // which is created lazily, on the first method invocation on the client proxy. + + FaultToleranceImpl(FaultToleranceStrategy strategy, EventHandlers eventHandlers, Initializer initializer) { this.strategy = strategy; this.eventHandlers = eventHandlers; + this.initializer = initializer; } @Override public T call(Callable action) throws Exception { + initializer.runOnce(); + InvocationContext ctx = new InvocationContext<>(action); eventHandlers.register(ctx); return strategy.apply(ctx); @@ -73,7 +99,7 @@ public static final class BuilderImpl implements Builder { private final Executor executor; private final Timer timer; private final EventLoop eventLoop; - private final CircuitBreakerMaintenanceInternal cbMaintenance; + private final BasicCircuitBreakerMaintenanceImpl cbMaintenance; private final boolean isAsync; private final Class asyncType; // ignored when isAsync == false private final Function, R> finisher; @@ -87,7 +113,7 @@ public static final class BuilderImpl implements Builder { private boolean offloadToAnotherThread; public BuilderImpl(boolean ftEnabled, Executor executor, Timer timer, EventLoop eventLoop, - CircuitBreakerMaintenanceInternal cbMaintenance, boolean isAsync, Class asyncType, + BasicCircuitBreakerMaintenanceImpl cbMaintenance, boolean isAsync, Class asyncType, Function, R> finisher) { this.ftEnabled = ftEnabled; this.executor = executor; @@ -144,10 +170,15 @@ public Builder withThreadOffload(boolean value) { @Override public R build() { + Consumer cbMaintenanceEventHandler = null; + if (circuitBreakerBuilder != null && circuitBreakerBuilder.name != null) { + cbMaintenanceEventHandler = cbMaintenance.stateTransitionEventHandler(circuitBreakerBuilder.name); + } EventHandlers eventHandlers = 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, @@ -158,16 +189,14 @@ public R build() { timeoutBuilder != null ? timeoutBuilder.onTimeout : null, timeoutBuilder != null ? timeoutBuilder.onFinished : null); - FaultTolerance result; - if (isAsync) { - result = new FaultToleranceImpl(buildAsyncStrategy(), eventHandlers); - } else { - result = new FaultToleranceImpl<>(buildSyncStrategy(), eventHandlers); - } + List initActions = new ArrayList<>(); + FaultToleranceStrategy strategy = isAsync ? buildAsyncStrategy(initActions) : buildSyncStrategy(initActions); + + FaultTolerance result = new FaultToleranceImpl<>(strategy, eventHandlers, new Initializer(initActions)); return finisher.apply(result); } - private FaultToleranceStrategy buildSyncStrategy() { + private FaultToleranceStrategy buildSyncStrategy(List initActions) { FaultToleranceStrategy result = invocation(); if (ftEnabled && bulkheadBuilder != null) { @@ -188,8 +217,14 @@ private FaultToleranceStrategy buildSyncStrategy() { circuitBreakerBuilder.successThreshold, new SystemStopwatch()); - String cbName = circuitBreakerBuilder.name != null ? circuitBreakerBuilder.name : UUID.randomUUID().toString(); - cbMaintenance.register(cbName, (CircuitBreaker) result); + if (circuitBreakerBuilder.name != null) { + cbMaintenance.registerName(circuitBreakerBuilder.name); + + CircuitBreaker circuitBreaker = (CircuitBreaker) result; + initActions.add(() -> { + cbMaintenance.register(circuitBreakerBuilder.name, circuitBreaker); + }); + } } if (ftEnabled && retryBuilder != null) { @@ -211,7 +246,7 @@ private FaultToleranceStrategy buildSyncStrategy() { return result; } - private FaultToleranceStrategy buildAsyncStrategy() { + private FaultToleranceStrategy buildAsyncStrategy(List initActions) { // FaultToleranceStrategy expects that the input type and output type are the same, which // isn't true for the conversion strategies used below (even though the entire chain does // retain the type, the conversions are only intermediate) @@ -221,7 +256,7 @@ private FaultToleranceStrategy buildAsyncStrategy() { result = new AsyncTypesConversion.ToCompletionStage(result, AsyncTypes.get(asyncType)); - result = buildCompletionStageChain(result); + result = buildCompletionStageChain(result, initActions); result = new AsyncTypesConversion.FromCompletionStage(result, AsyncTypes.get(asyncType)); @@ -229,7 +264,7 @@ private FaultToleranceStrategy buildAsyncStrategy() { } private FaultToleranceStrategy> buildCompletionStageChain( - FaultToleranceStrategy> invocation) { + FaultToleranceStrategy> invocation, List initActions) { FaultToleranceStrategy> result = invocation; // thread offload is always enabled @@ -255,7 +290,14 @@ private FaultToleranceStrategy> buildCompletionStageChain( circuitBreakerBuilder.successThreshold, new SystemStopwatch()); - cbMaintenance.register(description, (CircuitBreaker) result); + if (circuitBreakerBuilder.name != null) { + cbMaintenance.registerName(circuitBreakerBuilder.name); + + CircuitBreaker circuitBreaker = (CircuitBreaker) result; + initActions.add(() -> { + cbMaintenance.register(circuitBreakerBuilder.name, circuitBreaker); + }); + } } if (ftEnabled && retryBuilder != null) { diff --git a/implementation/core/src/main/java/io/smallrye/faulttolerance/core/util/Callbacks.java b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/util/Callbacks.java new file mode 100644 index 00000000..4e476e53 --- /dev/null +++ b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/util/Callbacks.java @@ -0,0 +1,35 @@ +package io.smallrye.faulttolerance.core.util; + +import java.util.function.Consumer; + +/** + * Utility methods for wrapping callbacks into variants that don't propagate exceptions. + */ +public class Callbacks { + public static Consumer wrap(Consumer callback) { + if (callback == null) { + return null; + } + + return value -> { + try { + callback.accept(value); + } catch (Exception ignored) { + } + }; + } + + public static Runnable wrap(Runnable callback) { + if (callback == null) { + return null; + } + + return () -> { + try { + callback.run(); + } catch (Exception ignored) { + } + }; + } + +} diff --git a/implementation/core/src/main/java/io/smallrye/faulttolerance/core/util/Initializer.java b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/util/Initializer.java new file mode 100644 index 00000000..d0c409d0 --- /dev/null +++ b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/util/Initializer.java @@ -0,0 +1,30 @@ +package io.smallrye.faulttolerance.core.util; + +import java.util.Collections; +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * Contains a sequence of {@link Runnable} actions and makes sure that they are only executed once. + */ +public final class Initializer { + private final Runnable[] actions; + private final AtomicBoolean ran = new AtomicBoolean(false); + + public Initializer(Runnable action) { + this(Collections.singletonList(action)); + } + + public Initializer(List actions) { + Preconditions.checkNotNull(actions, "List of actions must be set"); + this.actions = actions.toArray(new Runnable[0]); + } + + public void runOnce() { + if (ran.compareAndSet(false, true)) { + for (Runnable action : actions) { + action.run(); + } + } + } +} diff --git a/implementation/core/src/test/java/io/smallrye/faulttolerance/core/util/CallbacksTest.java b/implementation/core/src/test/java/io/smallrye/faulttolerance/core/util/CallbacksTest.java new file mode 100644 index 00000000..622d237a --- /dev/null +++ b/implementation/core/src/test/java/io/smallrye/faulttolerance/core/util/CallbacksTest.java @@ -0,0 +1,69 @@ +package io.smallrye.faulttolerance.core.util; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; + +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Consumer; + +import org.junit.jupiter.api.Test; + +public class CallbacksTest { + @Test + public void nullConsumer() { + assertThat(Callbacks.wrap((Consumer) null)).isNull(); + } + + @Test + public void consumer() { + AtomicReference value = new AtomicReference<>(); + + Callbacks.wrap(value::set).accept("hello"); + + assertThat(value).hasValue("hello"); + } + + @Test + public void throwingConsumer() { + AtomicReference value = new AtomicReference<>(); + + assertThatCode(() -> { + Callbacks.wrap((String string) -> { + value.set(string); + throw new RuntimeException(); + }).accept("hello"); + }).doesNotThrowAnyException(); + + assertThat(value).hasValue("hello"); + } + + @Test + public void nullRunnable() { + assertThat(Callbacks.wrap((Runnable) null)).isNull(); + } + + @Test + public void runnable() { + AtomicReference value = new AtomicReference<>(); + + Callbacks.wrap(() -> { + value.set("hello"); + }).run(); + + assertThat(value).hasValue("hello"); + } + + @Test + public void throwingRunnable() { + AtomicReference value = new AtomicReference<>(); + + assertThatCode(() -> { + Callbacks.wrap(() -> { + value.set("hello"); + throw new RuntimeException(); + }).run(); + }).doesNotThrowAnyException(); + + assertThat(value).hasValue("hello"); + } +} diff --git a/implementation/core/src/test/java/io/smallrye/faulttolerance/core/util/InitializerTest.java b/implementation/core/src/test/java/io/smallrye/faulttolerance/core/util/InitializerTest.java new file mode 100644 index 00000000..6151c285 --- /dev/null +++ b/implementation/core/src/test/java/io/smallrye/faulttolerance/core/util/InitializerTest.java @@ -0,0 +1,21 @@ +package io.smallrye.faulttolerance.core.util; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.concurrent.atomic.AtomicInteger; + +import org.junit.jupiter.api.Test; + +public class InitializerTest { + @Test + public void runOnce() { + AtomicInteger counter = new AtomicInteger(0); + Initializer initializer = new Initializer(counter::incrementAndGet); + + for (int i = 0; i < 1000; i++) { + initializer.runOnce(); + } + + assertThat(counter).hasValue(1); + } +} diff --git a/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/CircuitBreakerMaintenanceImpl.java b/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/CircuitBreakerMaintenanceImpl.java index a27e7451..32faa8ff 100644 --- a/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/CircuitBreakerMaintenanceImpl.java +++ b/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/CircuitBreakerMaintenanceImpl.java @@ -1,72 +1,14 @@ package io.smallrye.faulttolerance; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; - import javax.inject.Inject; import javax.inject.Singleton; -import io.smallrye.faulttolerance.api.CircuitBreakerState; -import io.smallrye.faulttolerance.core.apiimpl.CircuitBreakerMaintenanceInternal; -import io.smallrye.faulttolerance.core.circuit.breaker.CircuitBreaker; +import io.smallrye.faulttolerance.core.apiimpl.BasicCircuitBreakerMaintenanceImpl; @Singleton -public class CircuitBreakerMaintenanceImpl implements CircuitBreakerMaintenanceInternal { - private final ConcurrentMap> registry = new ConcurrentHashMap<>(); - - private final ExistingCircuitBreakerNames existingCircuitBreakerNames; - +public class CircuitBreakerMaintenanceImpl extends BasicCircuitBreakerMaintenanceImpl { @Inject public CircuitBreakerMaintenanceImpl(ExistingCircuitBreakerNames existingCircuitBreakerNames) { - this.existingCircuitBreakerNames = existingCircuitBreakerNames; - } - - @Override - public void register(String circuitBreakerName, CircuitBreaker circuitBreaker) { - registry.putIfAbsent(circuitBreakerName, circuitBreaker); - } - - @Override - public CircuitBreakerState currentState(String name) { - if (!existingCircuitBreakerNames.contains(name)) { - throw new IllegalArgumentException("Circuit breaker '" + name + "' doesn't exist"); - } - - CircuitBreaker circuitBreaker = registry.get(name); - if (circuitBreaker == null) { - // if the circuit breaker wasn't instantiated yet, it's "closed" by definition - return CircuitBreakerState.CLOSED; - } - - int currentState = circuitBreaker.currentState(); - switch (currentState) { - case CircuitBreaker.STATE_CLOSED: - return CircuitBreakerState.CLOSED; - case CircuitBreaker.STATE_OPEN: - return CircuitBreakerState.OPEN; - case CircuitBreaker.STATE_HALF_OPEN: - return CircuitBreakerState.HALF_OPEN; - default: - throw new IllegalStateException("Unknown circuit breaker state " + currentState); - } - } - - @Override - public void reset(String name) { - if (!existingCircuitBreakerNames.contains(name)) { - throw new IllegalArgumentException("Circuit breaker '" + name + "' doesn't exist"); - } - - CircuitBreaker circuitBreaker = registry.get(name); - if (circuitBreaker != null) { - // if the circuit breaker wasn't instantiated yet, "resetting it" is by definition a noop - circuitBreaker.reset(); - } - } - - @Override - public void resetAll() { - // circuit breakers that weren't instantiated yet don't have to be reset - registry.values().forEach(CircuitBreaker::reset); + super(existingCircuitBreakerNames::contains); } } 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 475ab18c..4584791f 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 @@ -57,6 +57,7 @@ import io.smallrye.faulttolerance.core.bulkhead.FutureThreadPoolBulkhead; import io.smallrye.faulttolerance.core.bulkhead.SemaphoreBulkhead; import io.smallrye.faulttolerance.core.circuit.breaker.CircuitBreaker; +import io.smallrye.faulttolerance.core.circuit.breaker.CircuitBreakerEvents; import io.smallrye.faulttolerance.core.circuit.breaker.CompletionStageCircuitBreaker; import io.smallrye.faulttolerance.core.event.loop.EventLoop; import io.smallrye.faulttolerance.core.fallback.AsyncFallbackFunction; @@ -170,7 +171,7 @@ private Object asyncFlow(FaultToleranceOperation operation, InvocationContext in InterceptionPoint point) { FaultToleranceStrategy strategy = cache.getStrategy(point, () -> prepareAsyncStrategy(operation, point)); - io.smallrye.faulttolerance.core.InvocationContext ctx = invocationContext(interceptionContext); + io.smallrye.faulttolerance.core.InvocationContext ctx = invocationContext(operation, interceptionContext); try { return strategy.apply(ctx); @@ -183,7 +184,7 @@ private T syncFlow(FaultToleranceOperation operation, InvocationContext inte throws Exception { FaultToleranceStrategy strategy = cache.getStrategy(point, () -> prepareSyncStrategy(operation, point)); - io.smallrye.faulttolerance.core.InvocationContext ctx = invocationContext(interceptionContext); + io.smallrye.faulttolerance.core.InvocationContext ctx = invocationContext(operation, interceptionContext); return strategy.apply(ctx); } @@ -192,18 +193,24 @@ private Future futureFlow(FaultToleranceOperation operation, InvocationCo InterceptionPoint point) throws Exception { FaultToleranceStrategy> strategy = cache.getStrategy(point, () -> prepareFutureStrategy(operation, point)); - io.smallrye.faulttolerance.core.InvocationContext> ctx = invocationContext(interceptionContext); + io.smallrye.faulttolerance.core.InvocationContext> ctx = invocationContext(operation, interceptionContext); return strategy.apply(ctx); } @SuppressWarnings("unchecked") - private io.smallrye.faulttolerance.core.InvocationContext invocationContext(InvocationContext interceptionContext) { + private io.smallrye.faulttolerance.core.InvocationContext invocationContext(FaultToleranceOperation operation, + InvocationContext interceptionContext) { io.smallrye.faulttolerance.core.InvocationContext result = new io.smallrye.faulttolerance.core.InvocationContext<>( () -> (T) interceptionContext.proceed()); result.set(InvocationContext.class, interceptionContext); + if (operation.hasCircuitBreaker() && operation.hasCircuitBreakerName()) { + result.registerEventHandler(CircuitBreakerEvents.StateTransition.class, + cbMaintenance.stateTransitionEventHandler(operation.getCircuitBreakerName().value())); + } + return result; } diff --git a/implementation/standalone/src/main/java/io/smallrye/faulttolerance/standalone/StandaloneCircuitBreakerMaintenance.java b/implementation/standalone/src/main/java/io/smallrye/faulttolerance/standalone/StandaloneCircuitBreakerMaintenance.java deleted file mode 100644 index 8e0ab451..00000000 --- a/implementation/standalone/src/main/java/io/smallrye/faulttolerance/standalone/StandaloneCircuitBreakerMaintenance.java +++ /dev/null @@ -1,53 +0,0 @@ -package io.smallrye.faulttolerance.standalone; - -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; - -import io.smallrye.faulttolerance.api.CircuitBreakerState; -import io.smallrye.faulttolerance.core.apiimpl.CircuitBreakerMaintenanceInternal; -import io.smallrye.faulttolerance.core.circuit.breaker.CircuitBreaker; - -class StandaloneCircuitBreakerMaintenance implements CircuitBreakerMaintenanceInternal { - private final ConcurrentMap> registry = new ConcurrentHashMap<>(); - - @Override - public void register(String circuitBreakerName, CircuitBreaker circuitBreaker) { - registry.putIfAbsent(circuitBreakerName, circuitBreaker); - } - - @Override - public CircuitBreakerState currentState(String name) { - CircuitBreaker circuitBreaker = registry.get(name); - if (circuitBreaker == null) { - // if the circuit breaker wasn't instantiated yet, it's "closed" by definition - return CircuitBreakerState.CLOSED; - } - - int currentState = circuitBreaker.currentState(); - switch (currentState) { - case CircuitBreaker.STATE_CLOSED: - return CircuitBreakerState.CLOSED; - case CircuitBreaker.STATE_OPEN: - return CircuitBreakerState.OPEN; - case CircuitBreaker.STATE_HALF_OPEN: - return CircuitBreakerState.HALF_OPEN; - default: - throw new IllegalStateException("Unknown circuit breaker state " + currentState); - } - } - - @Override - public void reset(String name) { - CircuitBreaker circuitBreaker = registry.get(name); - if (circuitBreaker != null) { - // if the circuit breaker wasn't instantiated yet, "resetting it" is by definition a noop - circuitBreaker.reset(); - } - } - - @Override - public void resetAll() { - // circuit breakers that weren't instantiated yet don't have to be reset - registry.values().forEach(CircuitBreaker::reset); - } -} diff --git a/implementation/standalone/src/main/java/io/smallrye/faulttolerance/standalone/StandaloneFaultToleranceSpi.java b/implementation/standalone/src/main/java/io/smallrye/faulttolerance/standalone/StandaloneFaultToleranceSpi.java index 3ec0e7d6..b9250f07 100644 --- a/implementation/standalone/src/main/java/io/smallrye/faulttolerance/standalone/StandaloneFaultToleranceSpi.java +++ b/implementation/standalone/src/main/java/io/smallrye/faulttolerance/standalone/StandaloneFaultToleranceSpi.java @@ -7,6 +7,7 @@ import io.smallrye.faulttolerance.api.CircuitBreakerMaintenance; import io.smallrye.faulttolerance.api.FaultTolerance; import io.smallrye.faulttolerance.api.FaultToleranceSpi; +import io.smallrye.faulttolerance.core.apiimpl.BasicCircuitBreakerMaintenanceImpl; import io.smallrye.faulttolerance.core.apiimpl.FaultToleranceImpl; import io.smallrye.faulttolerance.core.event.loop.EventLoop; import io.smallrye.faulttolerance.core.timer.Timer; @@ -20,7 +21,7 @@ static class Dependencies { final Timer timer = new Timer(executor); final EventLoop eventLoop = EventLoop.get(); - final StandaloneCircuitBreakerMaintenance cbMaintenance = new StandaloneCircuitBreakerMaintenance(); + final BasicCircuitBreakerMaintenanceImpl cbMaintenance = new BasicCircuitBreakerMaintenanceImpl(); } static class DependenciesHolder { 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 new file mode 100644 index 00000000..6354e3dc --- /dev/null +++ b/implementation/standalone/src/test/java/io/smallrye/faulttolerance/standalone/test/StandaloneCircuitBreakerMaintenanceTest.java @@ -0,0 +1,66 @@ +package io.smallrye.faulttolerance.standalone.test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.awaitility.Awaitility.await; + +import java.time.temporal.ChronoUnit; +import java.util.concurrent.Callable; +import java.util.concurrent.atomic.AtomicInteger; + +import org.eclipse.microprofile.faulttolerance.exceptions.CircuitBreakerOpenException; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import io.smallrye.faulttolerance.api.FaultTolerance; +import io.smallrye.faulttolerance.core.util.TestException; + +public class StandaloneCircuitBreakerMaintenanceTest { + @BeforeEach + public void setUp() { + FaultTolerance.circuitBreakerMaintenance().resetAll(); + } + + @Test + public void circuitBreakerEvents() throws Exception { + assertThatThrownBy(() -> { + FaultTolerance.circuitBreakerMaintenance().currentState("my-cb"); + }); + + Callable guarded = FaultTolerance.createCallable(this::action) + .withCircuitBreaker().requestVolumeThreshold(4).delay(1, ChronoUnit.SECONDS).name("my-cb").done() + .withFallback().handler(this::fallback).applyOn(CircuitBreakerOpenException.class).done() + .build(); + + AtomicInteger stateChanges = new AtomicInteger(); + FaultTolerance.circuitBreakerMaintenance().onStateChange("my-cb", ignored -> stateChanges.incrementAndGet()); + + assertThat(stateChanges).hasValue(0); + + for (int i = 0; i < 4; i++) { + assertThatCode(guarded::call).isExactlyInstanceOf(TestException.class); + } + + // 1. closed -> open + assertThat(stateChanges).hasValue(1); + + assertThat(guarded.call()).isEqualTo("fallback"); + + await().untilAsserted(() -> { + assertThatCode(guarded::call).isExactlyInstanceOf(TestException.class); + }); + + // 2. open -> half-open + // 3. half-open -> open + assertThat(stateChanges).hasValue(3); + } + + public String action() throws TestException { + throw new TestException(); + } + + public String fallback() { + return "fallback"; + } +} 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 new file mode 100644 index 00000000..bd5072c9 --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/circuitbreaker/maintenance/CircuitBreakerMaintenanceInteroperabilityTest.java @@ -0,0 +1,103 @@ +package io.smallrye.faulttolerance.circuitbreaker.maintenance; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.awaitility.Awaitility.await; + +import java.io.IOException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +import javax.inject.Inject; + +import org.eclipse.microprofile.faulttolerance.exceptions.CircuitBreakerOpenException; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junitpioneer.jupiter.SetSystemProperty; + +import io.smallrye.faulttolerance.api.CircuitBreakerMaintenance; +import io.smallrye.faulttolerance.api.CircuitBreakerState; +import io.smallrye.faulttolerance.api.FaultTolerance; +import io.smallrye.faulttolerance.util.FaultToleranceBasicTest; + +@FaultToleranceBasicTest +// Weld SE by default enables relaxed client proxy instantiation, which is different from +// all other environments and pretty crucial for this test +@SetSystemProperty(key = "org.jboss.weld.construction.relaxed", value = "false") +public class CircuitBreakerMaintenanceInteroperabilityTest { + @Inject + private HelloService helloService; + + @Inject + private CircuitBreakerMaintenance cb; + + @BeforeEach + public void reset() { + FaultTolerance.circuitBreakerMaintenance().resetAll(); + + helloService.toString(); // force bean instantiation + } + + @Test + public void test() { + CircuitBreakerMaintenance cbm = FaultTolerance.circuitBreakerMaintenance(); + + assertThat(cb.currentState("hello")).isEqualTo(CircuitBreakerState.CLOSED); + assertThat(cb.currentState("another-hello")).isEqualTo(CircuitBreakerState.CLOSED); + assertThat(cbm.currentState("hello")).isEqualTo(CircuitBreakerState.CLOSED); + assertThat(cbm.currentState("another-hello")).isEqualTo(CircuitBreakerState.CLOSED); + + AtomicInteger helloStateChanges = new AtomicInteger(); + AtomicInteger anotherHelloStateChanges = new AtomicInteger(); + + cbm.onStateChange("hello", ignored -> { + helloStateChanges.incrementAndGet(); + }); + cb.onStateChange("another-hello", ignored -> { + anotherHelloStateChanges.incrementAndGet(); + }); + + for (int i = 0; i < HelloService.THRESHOLD; i++) { + assertThatThrownBy(() -> { + helloService.hello(new IOException()); + }).isExactlyInstanceOf(IOException.class); + + assertThatThrownBy(() -> { + helloService.anotherHello(); + }).isExactlyInstanceOf(RuntimeException.class); + } + + assertThat(cb.currentState("hello")).isEqualTo(CircuitBreakerState.OPEN); + assertThat(cb.currentState("another-hello")).isEqualTo(CircuitBreakerState.OPEN); + assertThat(cbm.currentState("hello")).isEqualTo(CircuitBreakerState.OPEN); + assertThat(cbm.currentState("another-hello")).isEqualTo(CircuitBreakerState.OPEN); + + // hello 1. closed -> open + assertThat(helloStateChanges).hasValue(1); + // another-hello 1. closed -> open + assertThat(anotherHelloStateChanges).hasValue(1); + + await().atMost(HelloService.DELAY * 2, TimeUnit.MILLISECONDS) + .ignoreException(CircuitBreakerOpenException.class) + .untilAsserted(() -> { + assertThat(helloService.hello(null)).isEqualTo(HelloService.OK); + }); + await().atMost(HelloService.DELAY * 2, TimeUnit.MILLISECONDS) + .ignoreException(CircuitBreakerOpenException.class) + .untilAsserted(() -> { + assertThatThrownBy(helloService::anotherHello).isExactlyInstanceOf(RuntimeException.class); + }); + + assertThat(cb.currentState("hello")).isEqualTo(CircuitBreakerState.CLOSED); + assertThat(cb.currentState("another-hello")).isEqualTo(CircuitBreakerState.OPEN); + assertThat(cbm.currentState("hello")).isEqualTo(CircuitBreakerState.CLOSED); + assertThat(cbm.currentState("another-hello")).isEqualTo(CircuitBreakerState.OPEN); + + // hello 2. open -> half-open + // hello 3. half-open -> closed + assertThat(helloStateChanges).hasValue(3); + // another-hello 2. open -> half-open + // another-hello 3. half-open -> open + assertThat(anotherHelloStateChanges).hasValue(3); + } +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/circuitbreaker/maintenance/CircuitBreakerMaintenanceTest.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/circuitbreaker/maintenance/CircuitBreakerMaintenanceTest.java index 22535517..442555cf 100644 --- a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/circuitbreaker/maintenance/CircuitBreakerMaintenanceTest.java +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/circuitbreaker/maintenance/CircuitBreakerMaintenanceTest.java @@ -6,6 +6,7 @@ import java.io.IOException; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; import javax.inject.Inject; @@ -31,41 +32,41 @@ public void reset() { } @Test - public void readCircuitBreakerState() { - assertThat(cb.currentState("hello")).isEqualTo(CircuitBreakerState.CLOSED); - - for (int i = 0; i < HelloService.THRESHOLD; i++) { - assertThatThrownBy(() -> { - helloService.hello(new IOException()); - }).isExactlyInstanceOf(IOException.class); - } - - assertThat(cb.currentState("hello")).isEqualTo(CircuitBreakerState.OPEN); + public void readAndObserveCircuitBreakerState() throws Exception { + AtomicInteger stateChanges = new AtomicInteger(); + cb.onStateChange("hello", ignored -> { + stateChanges.incrementAndGet(); + }); - await().atMost(HelloService.DELAY * 2, TimeUnit.MILLISECONDS) - .ignoreException(CircuitBreakerOpenException.class) - .untilAsserted(() -> { - assertThat(helloService.hello(null)).isEqualTo(HelloService.OK); - }); + testCircuitBreaker(() -> { + await().atMost(HelloService.DELAY * 2, TimeUnit.MILLISECONDS) + .ignoreException(CircuitBreakerOpenException.class) + .untilAsserted(() -> { + assertThat(helloService.hello(null)).isEqualTo(HelloService.OK); + }); + }); - assertThat(cb.currentState("hello")).isEqualTo(CircuitBreakerState.CLOSED); + // 1. closed -> open + // 2. open -> half-open + // 3. half-open -> closed + assertThat(stateChanges).hasValue(3); } @Test public void resetCircuitBreaker() throws Exception { - testCircuitBreakerReset(() -> { + testCircuitBreaker(() -> { cb.reset("hello"); }); } @Test public void resetAllCircuitBreakers() throws Exception { - testCircuitBreakerReset(() -> { + testCircuitBreaker(() -> { cb.resetAll(); }); } - private void testCircuitBreakerReset(Runnable resetFunction) throws Exception { + private void testCircuitBreaker(Runnable resetFunction) throws Exception { assertThat(cb.currentState("hello")).isEqualTo(CircuitBreakerState.CLOSED); for (int i = 0; i < HelloService.THRESHOLD; i++) { 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 59c98aff..bc95125e 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 @@ -1,10 +1,14 @@ package io.smallrye.faulttolerance.circuitbreaker.maintenance; +import java.time.temporal.ChronoUnit; +import java.util.function.Supplier; + import javax.enterprise.context.ApplicationScoped; import org.eclipse.microprofile.faulttolerance.CircuitBreaker; import io.smallrye.faulttolerance.api.CircuitBreakerName; +import io.smallrye.faulttolerance.api.FaultTolerance; @ApplicationScoped public class HelloService { @@ -12,6 +16,10 @@ public class HelloService { static final int THRESHOLD = 5; static final int DELAY = 500; + private final Supplier anotherHello = FaultTolerance.createSupplier(this::anotherHelloImpl) + .withCircuitBreaker().requestVolumeThreshold(THRESHOLD).delay(DELAY, ChronoUnit.MILLIS).name("another-hello").done() + .build(); + @CircuitBreaker(requestVolumeThreshold = THRESHOLD, delay = DELAY) @CircuitBreakerName("hello") public String hello(Exception exception) throws Exception { @@ -21,4 +29,12 @@ public String hello(Exception exception) throws Exception { return OK; } + + public String anotherHello() { + return anotherHello.get(); + } + + private String anotherHelloImpl() { + throw new RuntimeException(); + } } diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/programmatic/CdiCircuitBreakerMaintenanceTest.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/programmatic/CdiCircuitBreakerMaintenanceTest.java new file mode 100644 index 00000000..d0defa64 --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/programmatic/CdiCircuitBreakerMaintenanceTest.java @@ -0,0 +1,8 @@ +package io.smallrye.faulttolerance.programmatic; + +import io.smallrye.faulttolerance.standalone.test.StandaloneCircuitBreakerMaintenanceTest; +import io.smallrye.faulttolerance.util.FaultToleranceBasicTest; + +@FaultToleranceBasicTest +public class CdiCircuitBreakerMaintenanceTest extends StandaloneCircuitBreakerMaintenanceTest { +} From 77aa3170dc1afd3486f26c729d190d5621c73ed3 Mon Sep 17 00:00:00 2001 From: Ladislav Thon Date: Tue, 8 Feb 2022 13:50:17 +0100 Subject: [PATCH 17/68] add Java 18-ea to CI builds --- .github/workflows/build.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 041899ea..7b0e6945 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -30,8 +30,7 @@ jobs: - 8 - 11 - 17 - # Temurin doesn't have an 18-ea build yet - #- 18-ea + - 18-ea os: - ubuntu-latest - windows-latest From 4137a06513f765321bb4ea44c2a0711d573e0742 Mon Sep 17 00:00:00 2001 From: Ladislav Thon Date: Tue, 8 Feb 2022 16:01:45 +0100 Subject: [PATCH 18/68] make CdiFaultToleranceSpi.Dependencies public to enable integration --- .../java/io/smallrye/faulttolerance/CdiFaultToleranceSpi.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/CdiFaultToleranceSpi.java b/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/CdiFaultToleranceSpi.java index b22b1b72..19796523 100644 --- a/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/CdiFaultToleranceSpi.java +++ b/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/CdiFaultToleranceSpi.java @@ -18,7 +18,7 @@ public class CdiFaultToleranceSpi implements FaultToleranceSpi { @Singleton - static class Dependencies { + public static class Dependencies { @Inject @ConfigProperty(name = "MP_Fault_Tolerance_NonFallback_Enabled", defaultValue = "true") boolean ftEnabled; From e2e63582193ac9ab94ea93b5619053aac9bb1b54 Mon Sep 17 00:00:00 2001 From: Ladislav Thon Date: Tue, 8 Feb 2022 17:32:36 +0100 Subject: [PATCH 19/68] add documentation for event listeners --- doc/modules/ROOT/pages/usage/extra.adoc | 5 ++++- .../ROOT/pages/usage/programmatic-api.adoc | 16 ++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/doc/modules/ROOT/pages/usage/extra.adoc b/doc/modules/ROOT/pages/usage/extra.adoc index 3e7b9d30..c16316a4 100644 --- a/doc/modules/ROOT/pages/usage/extra.adoc +++ b/doc/modules/ROOT/pages/usage/extra.adoc @@ -43,13 +43,16 @@ public class Example { <1> Obtains current circuit breaker state. <2> Resets all circuit breakers to the initial state. -The `CircuitBreakerMaintenance` interface provides 3 methods: +The `CircuitBreakerMaintenance` interface provides 4 methods: . `currentState(name)`: returns current state of given circuit breaker. The return type `CircuitBreakerState` is an `enum` with 3 values: `CLOSED`, `OPEN`, `HALF_OPEN`. +- `onStateChange(name, callback)`: registers a callback that will be called when given circuit breaker changes state. . `reset(name)`: resets given circuit breaker to the initial state. . `resetAll()`: resets all circuit breakers in the application to the initial state. +See the javadoc of those methods for more information. + [[blocking-nonblocking]] == @Blocking and @NonBlocking diff --git a/doc/modules/ROOT/pages/usage/programmatic-api.adoc b/doc/modules/ROOT/pages/usage/programmatic-api.adoc index 9f98f7dd..49184b1c 100644 --- a/doc/modules/ROOT/pages/usage/programmatic-api.adoc +++ b/doc/modules/ROOT/pages/usage/programmatic-api.adoc @@ -224,6 +224,22 @@ At the same time, this would _not_ be safe to do for all unnamed circuit breaker Therefore, all circuit breakers created using the programmatic API must be given a name when `CircuitBreakerMaintenance` is supposed to affect them. Note that duplicate names are not permitted and lead to an error, so lifecycle of the circuit breaker must be carefully considered. +=== Event Listeners + +The programmatic API has one feature that the declarative API doesn't have: ability to observe certain events. +For example, when configuring a circuit breaker, it is possible to register a callback for circuit breaker state changes or for a situation when an open circuit breaker prevents an invocation. +When configuring a timeout, it is possible to register a callback for when the invocation times out, etc. etc. +For example: + +[source,java] +---- +private static final FaultTolerance guard = FaultTolerance.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. + === Summary of `FaultTolerance` Methods There's a number of static `create*` methods on the `FaultTolerance` interface. From 43f6115001f3df2d5c6bba6705e64c8b2d75afc3 Mon Sep 17 00:00:00 2001 From: Ladislav Thon Date: Tue, 8 Feb 2022 18:01:15 +0100 Subject: [PATCH 20/68] release 5.3.0 --- .github/project.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/project.yml b/.github/project.yml index ea0c772a..ab4b9bd2 100644 --- a/.github/project.yml +++ b/.github/project.yml @@ -1,4 +1,4 @@ name: SmallRye Fault Tolerance release: - current-version: 5.2.1 - next-version: 5.2.2-SNAPSHOT + current-version: 5.3.0 + next-version: 5.3.1-SNAPSHOT From 76da6dc049a2b7c1de4100fe561f3aafbf70bcfa Mon Sep 17 00:00:00 2001 From: Ladislav Thon Date: Tue, 8 Feb 2022 20:10:01 +0100 Subject: [PATCH 21/68] avoid character entities that javadoc considers invalid --- .../java/io/smallrye/faulttolerance/api/FaultTolerance.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) 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 22edf346..72fad3b4 100644 --- a/api/src/main/java/io/smallrye/faulttolerance/api/FaultTolerance.java +++ b/api/src/main/java/io/smallrye/faulttolerance/api/FaultTolerance.java @@ -32,8 +32,7 @@ *

* 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 ➤ timeout ➤ bulkhead ➤ - * thread offload ➤ guarded action. + * in a predefined order: fallback > retry > circuit breaker > 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 From 8ec5b1b43cbdddf704609cd7fa124fee44f3dfce Mon Sep 17 00:00:00 2001 From: Ladislav Thon Date: Tue, 8 Feb 2022 20:31:27 +0100 Subject: [PATCH 22/68] release 5.3.0, second attempt --- .github/project.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/project.yml b/.github/project.yml index ab4b9bd2..2b2f4db8 100644 --- a/.github/project.yml +++ b/.github/project.yml @@ -2,3 +2,4 @@ name: SmallRye Fault Tolerance release: current-version: 5.3.0 next-version: 5.3.1-SNAPSHOT + From 5979bf50f4122d69f8c811b5e57c6599d9c2c600 Mon Sep 17 00:00:00 2001 From: Ladislav Thon Date: Tue, 8 Feb 2022 21:29:55 +0100 Subject: [PATCH 23/68] replace < and > with entities to please javadoc --- .../faulttolerance/api/FaultTolerance.java | 31 ++++++++++--------- 1 file changed, 16 insertions(+), 15 deletions(-) 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 72fad3b4..a9e68218 100644 --- a/api/src/main/java/io/smallrye/faulttolerance/api/FaultTolerance.java +++ b/api/src/main/java/io/smallrye/faulttolerance/api/FaultTolerance.java @@ -32,7 +32,8 @@ *

* 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 > timeout > bulkhead > thread offload > guarded action. + * in a predefined order: fallback > retry > circuit breaker > 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 @@ -340,7 +341,7 @@ interface BulkheadBuilder { /** * Sets the concurrency limit the bulkhead will enforce. Defaults to 10. * - * @param value the concurrency limit, must be >= 1 + * @param value the concurrency limit, must be >= 1 * @return this bulkhead builder */ BulkheadBuilder limit(int value); @@ -351,7 +352,7 @@ interface BulkheadBuilder { * 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 + * @param value the queue size, must be >= 1 * @return this bulkhead builder */ BulkheadBuilder queueSize(int value); @@ -448,7 +449,7 @@ default CircuitBreakerBuilder skipOn(Class 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 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 @@ -459,7 +460,7 @@ default CircuitBreakerBuilder skipOn(Class value) { /** * Sets the size of the circuit breaker's rolling window. * - * @param value the size of the circuit breaker's rolling window, must be >= 1 + * @param value the size of the circuit breaker's rolling window, must be >= 1 * @return this circuit breaker builder * @see CircuitBreaker#requestVolumeThreshold() @CircuitBreaker.requestVolumeThreshold */ @@ -468,7 +469,7 @@ default CircuitBreakerBuilder skipOn(Class 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 + * @param value the failure ratio, must be >= 0 and <= 1 * @return this circuit breaker builder * @see CircuitBreaker#failureRatio() @CircuitBreaker.failureRatio */ @@ -478,7 +479,7 @@ default CircuitBreakerBuilder skipOn(Class 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 + * @param value the number of successful executions, must be >= 1 * @return this circuit breaker builder * @see CircuitBreaker#successThreshold() @CircuitBreaker.successThreshold */ @@ -638,7 +639,7 @@ interface RetryBuilder { /** * Sets the maximum number of retries. Defaults to 3. * - * @param value the maximum number of retries, must be >= -1. + * @param value the maximum number of retries, must be >= -1. * @return this retry builder * @see Retry#maxRetries() @Retry.maxRetries */ @@ -647,7 +648,7 @@ interface RetryBuilder { /** * Sets the delay between retries. Defaults to 0. * - * @param value the delay length, must be >= 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 @@ -658,7 +659,7 @@ interface RetryBuilder { /** * Sets the maximum duration of all invocations, including possible retries. Defaults to 3 minutes. * - * @param value the maximum duration length, must be >= 0 + * @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 @@ -670,7 +671,7 @@ interface RetryBuilder { * 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 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 @@ -806,7 +807,7 @@ interface ExponentialBackoffBuilder { /** * Sets the multiplicative factor used to determine delay between retries. Defaults to 2. * - * @param value the multiplicative factor, must be >= 1 + * @param value the multiplicative factor, must be >= 1 * @return this exponential backoff builder * @see ExponentialBackoff#factor() @ExponentialBackoff.factor */ @@ -815,7 +816,7 @@ interface ExponentialBackoffBuilder { /** * Sets the maximum delay between retries. Defaults to 1 minute. * - * @param value the maximum delay, must be >= 0 + * @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 @@ -845,7 +846,7 @@ interface FibonacciBackoffBuilder { /** * Sets the maximum delay between retries. Defaults to 1 minute. * - * @param value the maximum delay, must be >= 0 + * @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 @@ -901,7 +902,7 @@ interface TimeoutBuilder { /** * Sets the timeout duration. Defaults to 1 second. * - * @param value the timeout length, must be >= 0 + * @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 From 496fb7342a81921df42eb6d5ebd116037fcacde5 Mon Sep 17 00:00:00 2001 From: Ladislav Thon Date: Tue, 8 Feb 2022 21:31:53 +0100 Subject: [PATCH 24/68] release 5.3.0, third attempt --- .github/project.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/project.yml b/.github/project.yml index 2b2f4db8..ab4b9bd2 100644 --- a/.github/project.yml +++ b/.github/project.yml @@ -2,4 +2,3 @@ name: SmallRye Fault Tolerance release: current-version: 5.3.0 next-version: 5.3.1-SNAPSHOT - From 3b792e7ad2873f18fb2f6854e9021acb2bd31d52 Mon Sep 17 00:00:00 2001 From: SmallRye CI Date: Tue, 8 Feb 2022 21:46:44 +0000 Subject: [PATCH 25/68] Update antora.yml before release --- doc/antora.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/antora.yml b/doc/antora.yml index a5e3cd35..582b75ea 100644 --- a/doc/antora.yml +++ b/doc/antora.yml @@ -1,16 +1,16 @@ name: smallrye-fault-tolerance title: SmallRye Fault Tolerance -version: main +version: 5.3.0 nav: - modules/ROOT/nav.adoc asciidoc: attributes: smallrye-fault-tolerance: SmallRye Fault Tolerance - smallrye-fault-tolerance-version: '5.2.1' + smallrye-fault-tolerance-version: '5.3.0' microprofile-fault-tolerance: MicroProfile Fault Tolerance microprofile-fault-tolerance-version: '3.0' microprofile-fault-tolerance-url: https://download.eclipse.org/microprofile/microprofile-fault-tolerance-3.0/microprofile-fault-tolerance-spec-3.0.html - vertx4-version: '4.1.1' + vertx4-version: '4.2.4' From c1201e47af75a305d36a415eebfd0dce70343266 Mon Sep 17 00:00:00 2001 From: SmallRye CI Date: Tue, 8 Feb 2022 21:48:13 +0000 Subject: [PATCH 26/68] [maven-release-plugin] prepare release 5.3.0 --- api/pom.xml | 2 +- implementation/autoconfig/core/pom.xml | 2 +- implementation/autoconfig/pom.xml | 2 +- implementation/autoconfig/processor/pom.xml | 2 +- implementation/context-propagation/pom.xml | 2 +- implementation/core/pom.xml | 2 +- implementation/fault-tolerance/pom.xml | 2 +- implementation/mutiny/pom.xml | 2 +- implementation/pom.xml | 2 +- implementation/rxjava3/pom.xml | 2 +- implementation/standalone/pom.xml | 2 +- implementation/tracing-propagation/pom.xml | 2 +- implementation/vertx/pom.xml | 2 +- pom.xml | 4 ++-- release/pom.xml | 2 +- testsuite/basic/pom.xml | 2 +- testsuite/integration/pom.xml | 2 +- testsuite/pom.xml | 2 +- testsuite/tck/pom.xml | 2 +- 19 files changed, 20 insertions(+), 20 deletions(-) diff --git a/api/pom.xml b/api/pom.xml index 395c2109..e763cabe 100644 --- a/api/pom.xml +++ b/api/pom.xml @@ -20,7 +20,7 @@ io.smallrye smallrye-fault-tolerance-parent - 5.2.2-SNAPSHOT + 5.3.0 smallrye-fault-tolerance-api diff --git a/implementation/autoconfig/core/pom.xml b/implementation/autoconfig/core/pom.xml index c6bfeca9..fc8f83c2 100644 --- a/implementation/autoconfig/core/pom.xml +++ b/implementation/autoconfig/core/pom.xml @@ -5,7 +5,7 @@ io.smallrye smallrye-fault-tolerance-autoconfig-parent - 5.2.2-SNAPSHOT + 5.3.0 smallrye-fault-tolerance-autoconfig-core diff --git a/implementation/autoconfig/pom.xml b/implementation/autoconfig/pom.xml index 8a327e1f..45d8a18a 100644 --- a/implementation/autoconfig/pom.xml +++ b/implementation/autoconfig/pom.xml @@ -5,7 +5,7 @@ io.smallrye smallrye-fault-tolerance-implementation-parent - 5.2.2-SNAPSHOT + 5.3.0 smallrye-fault-tolerance-autoconfig-parent diff --git a/implementation/autoconfig/processor/pom.xml b/implementation/autoconfig/processor/pom.xml index a2b35b12..7cbc8c6f 100644 --- a/implementation/autoconfig/processor/pom.xml +++ b/implementation/autoconfig/processor/pom.xml @@ -5,7 +5,7 @@ io.smallrye smallrye-fault-tolerance-autoconfig-parent - 5.2.2-SNAPSHOT + 5.3.0 smallrye-fault-tolerance-autoconfig-processor diff --git a/implementation/context-propagation/pom.xml b/implementation/context-propagation/pom.xml index 3fc14529..83b15fe0 100644 --- a/implementation/context-propagation/pom.xml +++ b/implementation/context-propagation/pom.xml @@ -20,7 +20,7 @@ io.smallrye smallrye-fault-tolerance-implementation-parent - 5.2.2-SNAPSHOT + 5.3.0 smallrye-fault-tolerance-context-propagation diff --git a/implementation/core/pom.xml b/implementation/core/pom.xml index b5e98d9a..566ba6eb 100644 --- a/implementation/core/pom.xml +++ b/implementation/core/pom.xml @@ -20,7 +20,7 @@ io.smallrye smallrye-fault-tolerance-implementation-parent - 5.2.2-SNAPSHOT + 5.3.0 smallrye-fault-tolerance-core diff --git a/implementation/fault-tolerance/pom.xml b/implementation/fault-tolerance/pom.xml index 816ad635..a0b751e0 100644 --- a/implementation/fault-tolerance/pom.xml +++ b/implementation/fault-tolerance/pom.xml @@ -20,7 +20,7 @@ io.smallrye smallrye-fault-tolerance-implementation-parent - 5.2.2-SNAPSHOT + 5.3.0 smallrye-fault-tolerance diff --git a/implementation/mutiny/pom.xml b/implementation/mutiny/pom.xml index 56fce4f6..327f5b03 100644 --- a/implementation/mutiny/pom.xml +++ b/implementation/mutiny/pom.xml @@ -5,7 +5,7 @@ io.smallrye smallrye-fault-tolerance-implementation-parent - 5.2.2-SNAPSHOT + 5.3.0 smallrye-fault-tolerance-mutiny diff --git a/implementation/pom.xml b/implementation/pom.xml index df5ef1ae..5c1864a1 100644 --- a/implementation/pom.xml +++ b/implementation/pom.xml @@ -20,7 +20,7 @@ io.smallrye smallrye-fault-tolerance-parent - 5.2.2-SNAPSHOT + 5.3.0 smallrye-fault-tolerance-implementation-parent diff --git a/implementation/rxjava3/pom.xml b/implementation/rxjava3/pom.xml index a1091891..ea776aa7 100644 --- a/implementation/rxjava3/pom.xml +++ b/implementation/rxjava3/pom.xml @@ -5,7 +5,7 @@ io.smallrye smallrye-fault-tolerance-implementation-parent - 5.2.2-SNAPSHOT + 5.3.0 smallrye-fault-tolerance-rxjava3 diff --git a/implementation/standalone/pom.xml b/implementation/standalone/pom.xml index 284ac947..017f5ae3 100644 --- a/implementation/standalone/pom.xml +++ b/implementation/standalone/pom.xml @@ -5,7 +5,7 @@ io.smallrye smallrye-fault-tolerance-implementation-parent - 5.2.2-SNAPSHOT + 5.3.0 smallrye-fault-tolerance-standalone diff --git a/implementation/tracing-propagation/pom.xml b/implementation/tracing-propagation/pom.xml index 44f81f58..dd4c6603 100644 --- a/implementation/tracing-propagation/pom.xml +++ b/implementation/tracing-propagation/pom.xml @@ -20,7 +20,7 @@ io.smallrye smallrye-fault-tolerance-implementation-parent - 5.2.2-SNAPSHOT + 5.3.0 smallrye-fault-tolerance-tracing-propagation diff --git a/implementation/vertx/pom.xml b/implementation/vertx/pom.xml index 8d2a51d4..eea2c140 100644 --- a/implementation/vertx/pom.xml +++ b/implementation/vertx/pom.xml @@ -20,7 +20,7 @@ io.smallrye smallrye-fault-tolerance-implementation-parent - 5.2.2-SNAPSHOT + 5.3.0 smallrye-fault-tolerance-vertx diff --git a/pom.xml b/pom.xml index 4c516458..7b945991 100644 --- a/pom.xml +++ b/pom.xml @@ -24,7 +24,7 @@ smallrye-fault-tolerance-parent - 5.2.2-SNAPSHOT + 5.3.0 pom SmallRye Fault Tolerance: Parent @@ -77,7 +77,7 @@ scm:git:git@github.com:smallrye/smallrye-fault-tolerance.git scm:git:git@github.com:smallrye/smallrye-fault-tolerance.git https://github.com/smallrye/smallrye-fault-tolerance/ - HEAD + 5.3.0 diff --git a/release/pom.xml b/release/pom.xml index 1adad09e..fe5ce6c9 100644 --- a/release/pom.xml +++ b/release/pom.xml @@ -5,7 +5,7 @@ io.smallrye smallrye-fault-tolerance-parent - 5.2.2-SNAPSHOT + 5.3.0 smallrye-fault-tolerance-release diff --git a/testsuite/basic/pom.xml b/testsuite/basic/pom.xml index 7494a38a..1e66238f 100644 --- a/testsuite/basic/pom.xml +++ b/testsuite/basic/pom.xml @@ -20,7 +20,7 @@ smallrye-fault-tolerance-testsuite-parent io.smallrye - 5.2.2-SNAPSHOT + 5.3.0 smallrye-fault-tolerance-testsuite-basic diff --git a/testsuite/integration/pom.xml b/testsuite/integration/pom.xml index 6afe32fe..b92b9340 100644 --- a/testsuite/integration/pom.xml +++ b/testsuite/integration/pom.xml @@ -5,7 +5,7 @@ smallrye-fault-tolerance-testsuite-parent io.smallrye - 5.2.2-SNAPSHOT + 5.3.0 smallrye-fault-tolerance-testsuite-integration diff --git a/testsuite/pom.xml b/testsuite/pom.xml index 1a97011e..e795d407 100644 --- a/testsuite/pom.xml +++ b/testsuite/pom.xml @@ -5,7 +5,7 @@ smallrye-fault-tolerance-parent io.smallrye - 5.2.2-SNAPSHOT + 5.3.0 smallrye-fault-tolerance-testsuite-parent diff --git a/testsuite/tck/pom.xml b/testsuite/tck/pom.xml index 47bbcfd2..e9641937 100644 --- a/testsuite/tck/pom.xml +++ b/testsuite/tck/pom.xml @@ -5,7 +5,7 @@ io.smallrye smallrye-fault-tolerance-testsuite-parent - 5.2.2-SNAPSHOT + 5.3.0 smallrye-fault-tolerance-tck From e62b809cf5f989a8d0a167af4fa63d22e02dc8e2 Mon Sep 17 00:00:00 2001 From: SmallRye CI Date: Tue, 8 Feb 2022 21:48:13 +0000 Subject: [PATCH 27/68] [maven-release-plugin] prepare for next development iteration --- api/pom.xml | 2 +- implementation/autoconfig/core/pom.xml | 2 +- implementation/autoconfig/pom.xml | 2 +- implementation/autoconfig/processor/pom.xml | 2 +- implementation/context-propagation/pom.xml | 2 +- implementation/core/pom.xml | 2 +- implementation/fault-tolerance/pom.xml | 2 +- implementation/mutiny/pom.xml | 2 +- implementation/pom.xml | 2 +- implementation/rxjava3/pom.xml | 2 +- implementation/standalone/pom.xml | 2 +- implementation/tracing-propagation/pom.xml | 2 +- implementation/vertx/pom.xml | 2 +- pom.xml | 4 ++-- release/pom.xml | 2 +- testsuite/basic/pom.xml | 2 +- testsuite/integration/pom.xml | 2 +- testsuite/pom.xml | 2 +- testsuite/tck/pom.xml | 2 +- 19 files changed, 20 insertions(+), 20 deletions(-) diff --git a/api/pom.xml b/api/pom.xml index e763cabe..c2aaf958 100644 --- a/api/pom.xml +++ b/api/pom.xml @@ -20,7 +20,7 @@ io.smallrye smallrye-fault-tolerance-parent - 5.3.0 + 5.3.1-SNAPSHOT smallrye-fault-tolerance-api diff --git a/implementation/autoconfig/core/pom.xml b/implementation/autoconfig/core/pom.xml index fc8f83c2..94fffa59 100644 --- a/implementation/autoconfig/core/pom.xml +++ b/implementation/autoconfig/core/pom.xml @@ -5,7 +5,7 @@ io.smallrye smallrye-fault-tolerance-autoconfig-parent - 5.3.0 + 5.3.1-SNAPSHOT smallrye-fault-tolerance-autoconfig-core diff --git a/implementation/autoconfig/pom.xml b/implementation/autoconfig/pom.xml index 45d8a18a..5237fbf2 100644 --- a/implementation/autoconfig/pom.xml +++ b/implementation/autoconfig/pom.xml @@ -5,7 +5,7 @@ io.smallrye smallrye-fault-tolerance-implementation-parent - 5.3.0 + 5.3.1-SNAPSHOT smallrye-fault-tolerance-autoconfig-parent diff --git a/implementation/autoconfig/processor/pom.xml b/implementation/autoconfig/processor/pom.xml index 7cbc8c6f..7bbc2063 100644 --- a/implementation/autoconfig/processor/pom.xml +++ b/implementation/autoconfig/processor/pom.xml @@ -5,7 +5,7 @@ io.smallrye smallrye-fault-tolerance-autoconfig-parent - 5.3.0 + 5.3.1-SNAPSHOT smallrye-fault-tolerance-autoconfig-processor diff --git a/implementation/context-propagation/pom.xml b/implementation/context-propagation/pom.xml index 83b15fe0..5c5d9edf 100644 --- a/implementation/context-propagation/pom.xml +++ b/implementation/context-propagation/pom.xml @@ -20,7 +20,7 @@ io.smallrye smallrye-fault-tolerance-implementation-parent - 5.3.0 + 5.3.1-SNAPSHOT smallrye-fault-tolerance-context-propagation diff --git a/implementation/core/pom.xml b/implementation/core/pom.xml index 566ba6eb..1d686326 100644 --- a/implementation/core/pom.xml +++ b/implementation/core/pom.xml @@ -20,7 +20,7 @@ io.smallrye smallrye-fault-tolerance-implementation-parent - 5.3.0 + 5.3.1-SNAPSHOT smallrye-fault-tolerance-core diff --git a/implementation/fault-tolerance/pom.xml b/implementation/fault-tolerance/pom.xml index a0b751e0..265832fe 100644 --- a/implementation/fault-tolerance/pom.xml +++ b/implementation/fault-tolerance/pom.xml @@ -20,7 +20,7 @@ io.smallrye smallrye-fault-tolerance-implementation-parent - 5.3.0 + 5.3.1-SNAPSHOT smallrye-fault-tolerance diff --git a/implementation/mutiny/pom.xml b/implementation/mutiny/pom.xml index 327f5b03..8ab999fa 100644 --- a/implementation/mutiny/pom.xml +++ b/implementation/mutiny/pom.xml @@ -5,7 +5,7 @@ io.smallrye smallrye-fault-tolerance-implementation-parent - 5.3.0 + 5.3.1-SNAPSHOT smallrye-fault-tolerance-mutiny diff --git a/implementation/pom.xml b/implementation/pom.xml index 5c1864a1..df0b1098 100644 --- a/implementation/pom.xml +++ b/implementation/pom.xml @@ -20,7 +20,7 @@ io.smallrye smallrye-fault-tolerance-parent - 5.3.0 + 5.3.1-SNAPSHOT smallrye-fault-tolerance-implementation-parent diff --git a/implementation/rxjava3/pom.xml b/implementation/rxjava3/pom.xml index ea776aa7..287d7442 100644 --- a/implementation/rxjava3/pom.xml +++ b/implementation/rxjava3/pom.xml @@ -5,7 +5,7 @@ io.smallrye smallrye-fault-tolerance-implementation-parent - 5.3.0 + 5.3.1-SNAPSHOT smallrye-fault-tolerance-rxjava3 diff --git a/implementation/standalone/pom.xml b/implementation/standalone/pom.xml index 017f5ae3..ee892574 100644 --- a/implementation/standalone/pom.xml +++ b/implementation/standalone/pom.xml @@ -5,7 +5,7 @@ io.smallrye smallrye-fault-tolerance-implementation-parent - 5.3.0 + 5.3.1-SNAPSHOT smallrye-fault-tolerance-standalone diff --git a/implementation/tracing-propagation/pom.xml b/implementation/tracing-propagation/pom.xml index dd4c6603..faba39c8 100644 --- a/implementation/tracing-propagation/pom.xml +++ b/implementation/tracing-propagation/pom.xml @@ -20,7 +20,7 @@ io.smallrye smallrye-fault-tolerance-implementation-parent - 5.3.0 + 5.3.1-SNAPSHOT smallrye-fault-tolerance-tracing-propagation diff --git a/implementation/vertx/pom.xml b/implementation/vertx/pom.xml index eea2c140..05155169 100644 --- a/implementation/vertx/pom.xml +++ b/implementation/vertx/pom.xml @@ -20,7 +20,7 @@ io.smallrye smallrye-fault-tolerance-implementation-parent - 5.3.0 + 5.3.1-SNAPSHOT smallrye-fault-tolerance-vertx diff --git a/pom.xml b/pom.xml index 7b945991..9b9e0a5c 100644 --- a/pom.xml +++ b/pom.xml @@ -24,7 +24,7 @@ smallrye-fault-tolerance-parent - 5.3.0 + 5.3.1-SNAPSHOT pom SmallRye Fault Tolerance: Parent @@ -77,7 +77,7 @@ scm:git:git@github.com:smallrye/smallrye-fault-tolerance.git scm:git:git@github.com:smallrye/smallrye-fault-tolerance.git https://github.com/smallrye/smallrye-fault-tolerance/ - 5.3.0 + HEAD diff --git a/release/pom.xml b/release/pom.xml index fe5ce6c9..4d8fbdc0 100644 --- a/release/pom.xml +++ b/release/pom.xml @@ -5,7 +5,7 @@ io.smallrye smallrye-fault-tolerance-parent - 5.3.0 + 5.3.1-SNAPSHOT smallrye-fault-tolerance-release diff --git a/testsuite/basic/pom.xml b/testsuite/basic/pom.xml index 1e66238f..d3b09f5d 100644 --- a/testsuite/basic/pom.xml +++ b/testsuite/basic/pom.xml @@ -20,7 +20,7 @@ smallrye-fault-tolerance-testsuite-parent io.smallrye - 5.3.0 + 5.3.1-SNAPSHOT smallrye-fault-tolerance-testsuite-basic diff --git a/testsuite/integration/pom.xml b/testsuite/integration/pom.xml index b92b9340..82378f42 100644 --- a/testsuite/integration/pom.xml +++ b/testsuite/integration/pom.xml @@ -5,7 +5,7 @@ smallrye-fault-tolerance-testsuite-parent io.smallrye - 5.3.0 + 5.3.1-SNAPSHOT smallrye-fault-tolerance-testsuite-integration diff --git a/testsuite/pom.xml b/testsuite/pom.xml index e795d407..8e6920b0 100644 --- a/testsuite/pom.xml +++ b/testsuite/pom.xml @@ -5,7 +5,7 @@ smallrye-fault-tolerance-parent io.smallrye - 5.3.0 + 5.3.1-SNAPSHOT smallrye-fault-tolerance-testsuite-parent diff --git a/testsuite/tck/pom.xml b/testsuite/tck/pom.xml index e9641937..c0030112 100644 --- a/testsuite/tck/pom.xml +++ b/testsuite/tck/pom.xml @@ -5,7 +5,7 @@ io.smallrye smallrye-fault-tolerance-testsuite-parent - 5.3.0 + 5.3.1-SNAPSHOT smallrye-fault-tolerance-tck From 62e82b5b89c9d577cb1780f2a05d3a3f3ee6efdd Mon Sep 17 00:00:00 2001 From: SmallRye CI Date: Tue, 8 Feb 2022 21:51:11 +0000 Subject: [PATCH 28/68] Update README.adoc and antora.yml after release --- README.adoc | 2 +- doc/antora.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.adoc b/README.adoc index 8a7dfb5b..dee24466 100644 --- a/README.adoc +++ b/README.adoc @@ -9,4 +9,4 @@ image:https://img.shields.io/maven-central/v/io.smallrye/smallrye-fault-toleranc == SmallRye Fault Tolerance SmallRye Fault Tolerance is an implementation of https://github.com/eclipse/microprofile-fault-tolerance/[Eclipse MicroProfile Fault Tolerance]. -See the https://smallrye.io/docs/smallrye-fault-tolerance/5.2.1/index.html[documentation]. +See the https://smallrye.io/docs/smallrye-fault-tolerance/5.3.0/index.html[documentation]. diff --git a/doc/antora.yml b/doc/antora.yml index 582b75ea..61c523a9 100644 --- a/doc/antora.yml +++ b/doc/antora.yml @@ -1,6 +1,6 @@ name: smallrye-fault-tolerance title: SmallRye Fault Tolerance -version: 5.3.0 +version: main nav: - modules/ROOT/nav.adoc From c32770ce3367d72d6a830a90dd5a206d45dc2203 Mon Sep 17 00:00:00 2001 From: Ladislav Thon Date: Wed, 9 Feb 2022 10:36:23 +0100 Subject: [PATCH 29/68] tiny improvements of the programmatic API documentation --- doc/modules/ROOT/pages/usage/programmatic-api.adoc | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/doc/modules/ROOT/pages/usage/programmatic-api.adoc b/doc/modules/ROOT/pages/usage/programmatic-api.adoc index 49184b1c..3502272d 100644 --- a/doc/modules/ROOT/pages/usage/programmatic-api.adoc +++ b/doc/modules/ROOT/pages/usage/programmatic-api.adoc @@ -120,6 +120,10 @@ In the example above, we assume the `externalService.hello()` call is blocking, If we didn't configure `withThreadOffload`, however, the execution 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. +Order of all the `with*` method invocations doesn’t matter. + === Synchronous vs. Asynchronous What's the difference between `FaultTolerance.>create()` and `FaultTolerance.createAsync()`? @@ -210,7 +214,7 @@ If you use the `adapt*` methods, the resulting `Callable`, `Supplier` or `Runnab 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. -==== Circuit Breaker Maintenance +=== 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. 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. @@ -240,6 +244,8 @@ private static final FaultTolerance guard = FaultTolerance.creat <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 There's a number of static `create*` methods on the `FaultTolerance` interface. From ef459208655358dc28a8dd93432d1098edc9d6a1 Mon Sep 17 00:00:00 2001 From: Radoslav Husar Date: Thu, 10 Feb 2022 12:51:22 +0100 Subject: [PATCH 30/68] Fix AsyncTypes to service load from the correct classloader instead of using the current thread context classloader This is because you cannot guarantee the implementation is going to be available to the thread. For instance, in the WildFly case, the implementation classes are not available in the deployment's classloader. This manifests as: Cannot deploy ftAsynchronous.war: {"WFLYCTL0062: Composite operation failed and was rolled back. Steps that failed:" => {"Operation step-1" => {"WFLYCTL0080: Failed services" => {"jboss.deployment.unit.\"ftAsynchronous.war\".WeldStartService" => "Failed to start service Caused by: org.jboss.weld.exceptions.DefinitionException: Exception List with 1 exceptions: Exception 0 : org.eclipse.microprofile.faulttolerance.exceptions.FaultToleranceDefinitionException: Invalid @Asynchronous on org.eclipse.microprofile.fault.tolerance.tck.asynchronous.AsyncClassLevelClient.serviceCS(java.util.concurrent.Future): must return java.util.concurrent.Future or --- .../smallrye/faulttolerance/core/async/types/AsyncTypes.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/implementation/core/src/main/java/io/smallrye/faulttolerance/core/async/types/AsyncTypes.java b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/async/types/AsyncTypes.java index 039c43b0..71f3a9d3 100644 --- a/implementation/core/src/main/java/io/smallrye/faulttolerance/core/async/types/AsyncTypes.java +++ b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/async/types/AsyncTypes.java @@ -11,7 +11,8 @@ public class AsyncTypes { static { Map, AsyncTypeConverter> map = new HashMap<>(); - for (AsyncTypeConverter converter : ServiceLoader.load(AsyncTypeConverter.class)) { + for (AsyncTypeConverter converter : ServiceLoader.load(AsyncTypeConverter.class, + AsyncTypes.class.getClassLoader())) { map.put(converter.type(), converter); } registry = Collections.unmodifiableMap(map); From 5cd88d124765b0dbe345e05fb277aa110acc6a44 Mon Sep 17 00:00:00 2001 From: Ladislav Thon Date: Thu, 10 Feb 2022 13:48:44 +0100 Subject: [PATCH 31/68] release 5.3.1 --- .github/project.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/project.yml b/.github/project.yml index ab4b9bd2..de9c37a9 100644 --- a/.github/project.yml +++ b/.github/project.yml @@ -1,4 +1,4 @@ name: SmallRye Fault Tolerance release: - current-version: 5.3.0 - next-version: 5.3.1-SNAPSHOT + current-version: 5.3.1 + next-version: 5.3.2-SNAPSHOT From af184affe7f715a650f4f62730e3cbfe60a42bd6 Mon Sep 17 00:00:00 2001 From: SmallRye CI Date: Thu, 10 Feb 2022 13:18:12 +0000 Subject: [PATCH 32/68] Update antora.yml before release --- doc/antora.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/antora.yml b/doc/antora.yml index 61c523a9..1c0a31c1 100644 --- a/doc/antora.yml +++ b/doc/antora.yml @@ -1,13 +1,13 @@ name: smallrye-fault-tolerance title: SmallRye Fault Tolerance -version: main +version: 5.3.1 nav: - modules/ROOT/nav.adoc asciidoc: attributes: smallrye-fault-tolerance: SmallRye Fault Tolerance - smallrye-fault-tolerance-version: '5.3.0' + smallrye-fault-tolerance-version: '5.3.1' microprofile-fault-tolerance: MicroProfile Fault Tolerance microprofile-fault-tolerance-version: '3.0' From d6859de22aa332bf40a590e05af0b12f8ed46d28 Mon Sep 17 00:00:00 2001 From: SmallRye CI Date: Thu, 10 Feb 2022 13:19:50 +0000 Subject: [PATCH 33/68] [maven-release-plugin] prepare release 5.3.1 --- api/pom.xml | 2 +- implementation/autoconfig/core/pom.xml | 2 +- implementation/autoconfig/pom.xml | 2 +- implementation/autoconfig/processor/pom.xml | 2 +- implementation/context-propagation/pom.xml | 2 +- implementation/core/pom.xml | 2 +- implementation/fault-tolerance/pom.xml | 2 +- implementation/mutiny/pom.xml | 2 +- implementation/pom.xml | 2 +- implementation/rxjava3/pom.xml | 2 +- implementation/standalone/pom.xml | 2 +- implementation/tracing-propagation/pom.xml | 2 +- implementation/vertx/pom.xml | 2 +- pom.xml | 4 ++-- release/pom.xml | 2 +- testsuite/basic/pom.xml | 2 +- testsuite/integration/pom.xml | 2 +- testsuite/pom.xml | 2 +- testsuite/tck/pom.xml | 2 +- 19 files changed, 20 insertions(+), 20 deletions(-) diff --git a/api/pom.xml b/api/pom.xml index c2aaf958..f2828d75 100644 --- a/api/pom.xml +++ b/api/pom.xml @@ -20,7 +20,7 @@ io.smallrye smallrye-fault-tolerance-parent - 5.3.1-SNAPSHOT + 5.3.1 smallrye-fault-tolerance-api diff --git a/implementation/autoconfig/core/pom.xml b/implementation/autoconfig/core/pom.xml index 94fffa59..4cb3c921 100644 --- a/implementation/autoconfig/core/pom.xml +++ b/implementation/autoconfig/core/pom.xml @@ -5,7 +5,7 @@ io.smallrye smallrye-fault-tolerance-autoconfig-parent - 5.3.1-SNAPSHOT + 5.3.1 smallrye-fault-tolerance-autoconfig-core diff --git a/implementation/autoconfig/pom.xml b/implementation/autoconfig/pom.xml index 5237fbf2..2ab70273 100644 --- a/implementation/autoconfig/pom.xml +++ b/implementation/autoconfig/pom.xml @@ -5,7 +5,7 @@ io.smallrye smallrye-fault-tolerance-implementation-parent - 5.3.1-SNAPSHOT + 5.3.1 smallrye-fault-tolerance-autoconfig-parent diff --git a/implementation/autoconfig/processor/pom.xml b/implementation/autoconfig/processor/pom.xml index 7bbc2063..8ddfbcac 100644 --- a/implementation/autoconfig/processor/pom.xml +++ b/implementation/autoconfig/processor/pom.xml @@ -5,7 +5,7 @@ io.smallrye smallrye-fault-tolerance-autoconfig-parent - 5.3.1-SNAPSHOT + 5.3.1 smallrye-fault-tolerance-autoconfig-processor diff --git a/implementation/context-propagation/pom.xml b/implementation/context-propagation/pom.xml index 5c5d9edf..beaa7bd7 100644 --- a/implementation/context-propagation/pom.xml +++ b/implementation/context-propagation/pom.xml @@ -20,7 +20,7 @@ io.smallrye smallrye-fault-tolerance-implementation-parent - 5.3.1-SNAPSHOT + 5.3.1 smallrye-fault-tolerance-context-propagation diff --git a/implementation/core/pom.xml b/implementation/core/pom.xml index 1d686326..57438e6b 100644 --- a/implementation/core/pom.xml +++ b/implementation/core/pom.xml @@ -20,7 +20,7 @@ io.smallrye smallrye-fault-tolerance-implementation-parent - 5.3.1-SNAPSHOT + 5.3.1 smallrye-fault-tolerance-core diff --git a/implementation/fault-tolerance/pom.xml b/implementation/fault-tolerance/pom.xml index 265832fe..324e82a6 100644 --- a/implementation/fault-tolerance/pom.xml +++ b/implementation/fault-tolerance/pom.xml @@ -20,7 +20,7 @@ io.smallrye smallrye-fault-tolerance-implementation-parent - 5.3.1-SNAPSHOT + 5.3.1 smallrye-fault-tolerance diff --git a/implementation/mutiny/pom.xml b/implementation/mutiny/pom.xml index 8ab999fa..bf96e6dd 100644 --- a/implementation/mutiny/pom.xml +++ b/implementation/mutiny/pom.xml @@ -5,7 +5,7 @@ io.smallrye smallrye-fault-tolerance-implementation-parent - 5.3.1-SNAPSHOT + 5.3.1 smallrye-fault-tolerance-mutiny diff --git a/implementation/pom.xml b/implementation/pom.xml index df0b1098..c99ea5e3 100644 --- a/implementation/pom.xml +++ b/implementation/pom.xml @@ -20,7 +20,7 @@ io.smallrye smallrye-fault-tolerance-parent - 5.3.1-SNAPSHOT + 5.3.1 smallrye-fault-tolerance-implementation-parent diff --git a/implementation/rxjava3/pom.xml b/implementation/rxjava3/pom.xml index 287d7442..803ea741 100644 --- a/implementation/rxjava3/pom.xml +++ b/implementation/rxjava3/pom.xml @@ -5,7 +5,7 @@ io.smallrye smallrye-fault-tolerance-implementation-parent - 5.3.1-SNAPSHOT + 5.3.1 smallrye-fault-tolerance-rxjava3 diff --git a/implementation/standalone/pom.xml b/implementation/standalone/pom.xml index ee892574..7d02bbf6 100644 --- a/implementation/standalone/pom.xml +++ b/implementation/standalone/pom.xml @@ -5,7 +5,7 @@ io.smallrye smallrye-fault-tolerance-implementation-parent - 5.3.1-SNAPSHOT + 5.3.1 smallrye-fault-tolerance-standalone diff --git a/implementation/tracing-propagation/pom.xml b/implementation/tracing-propagation/pom.xml index faba39c8..b710547a 100644 --- a/implementation/tracing-propagation/pom.xml +++ b/implementation/tracing-propagation/pom.xml @@ -20,7 +20,7 @@ io.smallrye smallrye-fault-tolerance-implementation-parent - 5.3.1-SNAPSHOT + 5.3.1 smallrye-fault-tolerance-tracing-propagation diff --git a/implementation/vertx/pom.xml b/implementation/vertx/pom.xml index 05155169..19855f3a 100644 --- a/implementation/vertx/pom.xml +++ b/implementation/vertx/pom.xml @@ -20,7 +20,7 @@ io.smallrye smallrye-fault-tolerance-implementation-parent - 5.3.1-SNAPSHOT + 5.3.1 smallrye-fault-tolerance-vertx diff --git a/pom.xml b/pom.xml index 9b9e0a5c..106ce955 100644 --- a/pom.xml +++ b/pom.xml @@ -24,7 +24,7 @@ smallrye-fault-tolerance-parent - 5.3.1-SNAPSHOT + 5.3.1 pom SmallRye Fault Tolerance: Parent @@ -77,7 +77,7 @@ scm:git:git@github.com:smallrye/smallrye-fault-tolerance.git scm:git:git@github.com:smallrye/smallrye-fault-tolerance.git https://github.com/smallrye/smallrye-fault-tolerance/ - HEAD + 5.3.1 diff --git a/release/pom.xml b/release/pom.xml index 4d8fbdc0..e745b390 100644 --- a/release/pom.xml +++ b/release/pom.xml @@ -5,7 +5,7 @@ io.smallrye smallrye-fault-tolerance-parent - 5.3.1-SNAPSHOT + 5.3.1 smallrye-fault-tolerance-release diff --git a/testsuite/basic/pom.xml b/testsuite/basic/pom.xml index d3b09f5d..69bb2bde 100644 --- a/testsuite/basic/pom.xml +++ b/testsuite/basic/pom.xml @@ -20,7 +20,7 @@ smallrye-fault-tolerance-testsuite-parent io.smallrye - 5.3.1-SNAPSHOT + 5.3.1 smallrye-fault-tolerance-testsuite-basic diff --git a/testsuite/integration/pom.xml b/testsuite/integration/pom.xml index 82378f42..90783314 100644 --- a/testsuite/integration/pom.xml +++ b/testsuite/integration/pom.xml @@ -5,7 +5,7 @@ smallrye-fault-tolerance-testsuite-parent io.smallrye - 5.3.1-SNAPSHOT + 5.3.1 smallrye-fault-tolerance-testsuite-integration diff --git a/testsuite/pom.xml b/testsuite/pom.xml index 8e6920b0..19a665cf 100644 --- a/testsuite/pom.xml +++ b/testsuite/pom.xml @@ -5,7 +5,7 @@ smallrye-fault-tolerance-parent io.smallrye - 5.3.1-SNAPSHOT + 5.3.1 smallrye-fault-tolerance-testsuite-parent diff --git a/testsuite/tck/pom.xml b/testsuite/tck/pom.xml index c0030112..74c85dd6 100644 --- a/testsuite/tck/pom.xml +++ b/testsuite/tck/pom.xml @@ -5,7 +5,7 @@ io.smallrye smallrye-fault-tolerance-testsuite-parent - 5.3.1-SNAPSHOT + 5.3.1 smallrye-fault-tolerance-tck From 0130066ed5f0dd9ebc28ba4ec82e7553d9620c4c Mon Sep 17 00:00:00 2001 From: SmallRye CI Date: Thu, 10 Feb 2022 13:19:50 +0000 Subject: [PATCH 34/68] [maven-release-plugin] prepare for next development iteration --- api/pom.xml | 2 +- implementation/autoconfig/core/pom.xml | 2 +- implementation/autoconfig/pom.xml | 2 +- implementation/autoconfig/processor/pom.xml | 2 +- implementation/context-propagation/pom.xml | 2 +- implementation/core/pom.xml | 2 +- implementation/fault-tolerance/pom.xml | 2 +- implementation/mutiny/pom.xml | 2 +- implementation/pom.xml | 2 +- implementation/rxjava3/pom.xml | 2 +- implementation/standalone/pom.xml | 2 +- implementation/tracing-propagation/pom.xml | 2 +- implementation/vertx/pom.xml | 2 +- pom.xml | 4 ++-- release/pom.xml | 2 +- testsuite/basic/pom.xml | 2 +- testsuite/integration/pom.xml | 2 +- testsuite/pom.xml | 2 +- testsuite/tck/pom.xml | 2 +- 19 files changed, 20 insertions(+), 20 deletions(-) diff --git a/api/pom.xml b/api/pom.xml index f2828d75..599539b9 100644 --- a/api/pom.xml +++ b/api/pom.xml @@ -20,7 +20,7 @@ io.smallrye smallrye-fault-tolerance-parent - 5.3.1 + 5.3.2-SNAPSHOT smallrye-fault-tolerance-api diff --git a/implementation/autoconfig/core/pom.xml b/implementation/autoconfig/core/pom.xml index 4cb3c921..0fd01ab0 100644 --- a/implementation/autoconfig/core/pom.xml +++ b/implementation/autoconfig/core/pom.xml @@ -5,7 +5,7 @@ io.smallrye smallrye-fault-tolerance-autoconfig-parent - 5.3.1 + 5.3.2-SNAPSHOT smallrye-fault-tolerance-autoconfig-core diff --git a/implementation/autoconfig/pom.xml b/implementation/autoconfig/pom.xml index 2ab70273..55219b82 100644 --- a/implementation/autoconfig/pom.xml +++ b/implementation/autoconfig/pom.xml @@ -5,7 +5,7 @@ io.smallrye smallrye-fault-tolerance-implementation-parent - 5.3.1 + 5.3.2-SNAPSHOT smallrye-fault-tolerance-autoconfig-parent diff --git a/implementation/autoconfig/processor/pom.xml b/implementation/autoconfig/processor/pom.xml index 8ddfbcac..130750ec 100644 --- a/implementation/autoconfig/processor/pom.xml +++ b/implementation/autoconfig/processor/pom.xml @@ -5,7 +5,7 @@ io.smallrye smallrye-fault-tolerance-autoconfig-parent - 5.3.1 + 5.3.2-SNAPSHOT smallrye-fault-tolerance-autoconfig-processor diff --git a/implementation/context-propagation/pom.xml b/implementation/context-propagation/pom.xml index beaa7bd7..a374f90c 100644 --- a/implementation/context-propagation/pom.xml +++ b/implementation/context-propagation/pom.xml @@ -20,7 +20,7 @@ io.smallrye smallrye-fault-tolerance-implementation-parent - 5.3.1 + 5.3.2-SNAPSHOT smallrye-fault-tolerance-context-propagation diff --git a/implementation/core/pom.xml b/implementation/core/pom.xml index 57438e6b..04167556 100644 --- a/implementation/core/pom.xml +++ b/implementation/core/pom.xml @@ -20,7 +20,7 @@ io.smallrye smallrye-fault-tolerance-implementation-parent - 5.3.1 + 5.3.2-SNAPSHOT smallrye-fault-tolerance-core diff --git a/implementation/fault-tolerance/pom.xml b/implementation/fault-tolerance/pom.xml index 324e82a6..7bb58bc5 100644 --- a/implementation/fault-tolerance/pom.xml +++ b/implementation/fault-tolerance/pom.xml @@ -20,7 +20,7 @@ io.smallrye smallrye-fault-tolerance-implementation-parent - 5.3.1 + 5.3.2-SNAPSHOT smallrye-fault-tolerance diff --git a/implementation/mutiny/pom.xml b/implementation/mutiny/pom.xml index bf96e6dd..f8aa7d6c 100644 --- a/implementation/mutiny/pom.xml +++ b/implementation/mutiny/pom.xml @@ -5,7 +5,7 @@ io.smallrye smallrye-fault-tolerance-implementation-parent - 5.3.1 + 5.3.2-SNAPSHOT smallrye-fault-tolerance-mutiny diff --git a/implementation/pom.xml b/implementation/pom.xml index c99ea5e3..af999e0c 100644 --- a/implementation/pom.xml +++ b/implementation/pom.xml @@ -20,7 +20,7 @@ io.smallrye smallrye-fault-tolerance-parent - 5.3.1 + 5.3.2-SNAPSHOT smallrye-fault-tolerance-implementation-parent diff --git a/implementation/rxjava3/pom.xml b/implementation/rxjava3/pom.xml index 803ea741..b5588c96 100644 --- a/implementation/rxjava3/pom.xml +++ b/implementation/rxjava3/pom.xml @@ -5,7 +5,7 @@ io.smallrye smallrye-fault-tolerance-implementation-parent - 5.3.1 + 5.3.2-SNAPSHOT smallrye-fault-tolerance-rxjava3 diff --git a/implementation/standalone/pom.xml b/implementation/standalone/pom.xml index 7d02bbf6..4782d3cd 100644 --- a/implementation/standalone/pom.xml +++ b/implementation/standalone/pom.xml @@ -5,7 +5,7 @@ io.smallrye smallrye-fault-tolerance-implementation-parent - 5.3.1 + 5.3.2-SNAPSHOT smallrye-fault-tolerance-standalone diff --git a/implementation/tracing-propagation/pom.xml b/implementation/tracing-propagation/pom.xml index b710547a..654fe873 100644 --- a/implementation/tracing-propagation/pom.xml +++ b/implementation/tracing-propagation/pom.xml @@ -20,7 +20,7 @@ io.smallrye smallrye-fault-tolerance-implementation-parent - 5.3.1 + 5.3.2-SNAPSHOT smallrye-fault-tolerance-tracing-propagation diff --git a/implementation/vertx/pom.xml b/implementation/vertx/pom.xml index 19855f3a..f77a6681 100644 --- a/implementation/vertx/pom.xml +++ b/implementation/vertx/pom.xml @@ -20,7 +20,7 @@ io.smallrye smallrye-fault-tolerance-implementation-parent - 5.3.1 + 5.3.2-SNAPSHOT smallrye-fault-tolerance-vertx diff --git a/pom.xml b/pom.xml index 106ce955..76ae2678 100644 --- a/pom.xml +++ b/pom.xml @@ -24,7 +24,7 @@ smallrye-fault-tolerance-parent - 5.3.1 + 5.3.2-SNAPSHOT pom SmallRye Fault Tolerance: Parent @@ -77,7 +77,7 @@ scm:git:git@github.com:smallrye/smallrye-fault-tolerance.git scm:git:git@github.com:smallrye/smallrye-fault-tolerance.git https://github.com/smallrye/smallrye-fault-tolerance/ - 5.3.1 + HEAD diff --git a/release/pom.xml b/release/pom.xml index e745b390..70a4b933 100644 --- a/release/pom.xml +++ b/release/pom.xml @@ -5,7 +5,7 @@ io.smallrye smallrye-fault-tolerance-parent - 5.3.1 + 5.3.2-SNAPSHOT smallrye-fault-tolerance-release diff --git a/testsuite/basic/pom.xml b/testsuite/basic/pom.xml index 69bb2bde..d1638917 100644 --- a/testsuite/basic/pom.xml +++ b/testsuite/basic/pom.xml @@ -20,7 +20,7 @@ smallrye-fault-tolerance-testsuite-parent io.smallrye - 5.3.1 + 5.3.2-SNAPSHOT smallrye-fault-tolerance-testsuite-basic diff --git a/testsuite/integration/pom.xml b/testsuite/integration/pom.xml index 90783314..60488060 100644 --- a/testsuite/integration/pom.xml +++ b/testsuite/integration/pom.xml @@ -5,7 +5,7 @@ smallrye-fault-tolerance-testsuite-parent io.smallrye - 5.3.1 + 5.3.2-SNAPSHOT smallrye-fault-tolerance-testsuite-integration diff --git a/testsuite/pom.xml b/testsuite/pom.xml index 19a665cf..a8c418b5 100644 --- a/testsuite/pom.xml +++ b/testsuite/pom.xml @@ -5,7 +5,7 @@ smallrye-fault-tolerance-parent io.smallrye - 5.3.1 + 5.3.2-SNAPSHOT smallrye-fault-tolerance-testsuite-parent diff --git a/testsuite/tck/pom.xml b/testsuite/tck/pom.xml index 74c85dd6..4d816259 100644 --- a/testsuite/tck/pom.xml +++ b/testsuite/tck/pom.xml @@ -5,7 +5,7 @@ io.smallrye smallrye-fault-tolerance-testsuite-parent - 5.3.1 + 5.3.2-SNAPSHOT smallrye-fault-tolerance-tck From 662bd11da0583359e7974ed4a001cad4cafeda7d Mon Sep 17 00:00:00 2001 From: SmallRye CI Date: Thu, 10 Feb 2022 13:24:20 +0000 Subject: [PATCH 35/68] Update README.adoc and antora.yml after release --- README.adoc | 2 +- doc/antora.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.adoc b/README.adoc index dee24466..531cd513 100644 --- a/README.adoc +++ b/README.adoc @@ -9,4 +9,4 @@ image:https://img.shields.io/maven-central/v/io.smallrye/smallrye-fault-toleranc == SmallRye Fault Tolerance SmallRye Fault Tolerance is an implementation of https://github.com/eclipse/microprofile-fault-tolerance/[Eclipse MicroProfile Fault Tolerance]. -See the https://smallrye.io/docs/smallrye-fault-tolerance/5.3.0/index.html[documentation]. +See the https://smallrye.io/docs/smallrye-fault-tolerance/5.3.1/index.html[documentation]. diff --git a/doc/antora.yml b/doc/antora.yml index 1c0a31c1..661a3c7f 100644 --- a/doc/antora.yml +++ b/doc/antora.yml @@ -1,6 +1,6 @@ name: smallrye-fault-tolerance title: SmallRye Fault Tolerance -version: 5.3.1 +version: main nav: - modules/ROOT/nav.adoc From 11f757f799368a90643e29e4902130f921d6d64f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Feb 2022 07:09:35 +0000 Subject: [PATCH 36/68] Bump smallrye-config from 2.8.2 to 2.9.0 Bumps [smallrye-config](https://github.com/smallrye/smallrye-config) from 2.8.2 to 2.9.0. - [Release notes](https://github.com/smallrye/smallrye-config/releases) - [Commits](https://github.com/smallrye/smallrye-config/compare/2.8.2...2.9.0) --- updated-dependencies: - dependency-name: io.smallrye.config:smallrye-config dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 76ae2678..0ce47959 100644 --- a/pom.xml +++ b/pom.xml @@ -42,7 +42,7 @@ 3.0 1.2 - 2.8.2 + 2.9.0 3.0.4 From ddaf388a57fb9908aa64f66e17b41eec94532cda Mon Sep 17 00:00:00 2001 From: Radoslav Husar Date: Mon, 14 Feb 2022 15:19:37 +0100 Subject: [PATCH 37/68] AsyncTypes class needs to service load in a privileged block to work with security manager enabled. --- .../faulttolerance/core/async/types/AsyncTypes.java | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/implementation/core/src/main/java/io/smallrye/faulttolerance/core/async/types/AsyncTypes.java b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/async/types/AsyncTypes.java index 71f3a9d3..a24f6013 100644 --- a/implementation/core/src/main/java/io/smallrye/faulttolerance/core/async/types/AsyncTypes.java +++ b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/async/types/AsyncTypes.java @@ -1,5 +1,7 @@ package io.smallrye.faulttolerance.core.async.types; +import java.security.AccessController; +import java.security.PrivilegedAction; import java.util.Collection; import java.util.Collections; import java.util.HashMap; @@ -11,13 +13,19 @@ public class AsyncTypes { static { Map, AsyncTypeConverter> map = new HashMap<>(); - for (AsyncTypeConverter converter : ServiceLoader.load(AsyncTypeConverter.class, - AsyncTypes.class.getClassLoader())) { + Iterable converters = (System.getSecurityManager() != null) + ? AccessController.doPrivileged((PrivilegedAction>) AsyncTypes::loadConverters) + : loadConverters(); + for (AsyncTypeConverter converter : converters) { map.put(converter.type(), converter); } registry = Collections.unmodifiableMap(map); } + private static ServiceLoader loadConverters() { + return ServiceLoader.load(AsyncTypeConverter.class, AsyncTypeConverter.class.getClassLoader()); + } + public static boolean isKnown(Class type) { return registry.containsKey(type); } From e2686beec442809761ac9851853d57f3cb9ee234 Mon Sep 17 00:00:00 2001 From: Ladislav Thon Date: Mon, 14 Feb 2022 16:40:18 +0100 Subject: [PATCH 38/68] release 5.3.2 --- .github/project.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/project.yml b/.github/project.yml index de9c37a9..28d276fe 100644 --- a/.github/project.yml +++ b/.github/project.yml @@ -1,4 +1,4 @@ name: SmallRye Fault Tolerance release: - current-version: 5.3.1 - next-version: 5.3.2-SNAPSHOT + current-version: 5.3.2 + next-version: 5.3.3-SNAPSHOT From 75307a498fba19441a23cfb0dfc9025cb81662c5 Mon Sep 17 00:00:00 2001 From: SmallRye CI Date: Mon, 14 Feb 2022 16:10:46 +0000 Subject: [PATCH 39/68] Update antora.yml before release --- doc/antora.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/antora.yml b/doc/antora.yml index 661a3c7f..003ec02c 100644 --- a/doc/antora.yml +++ b/doc/antora.yml @@ -1,13 +1,13 @@ name: smallrye-fault-tolerance title: SmallRye Fault Tolerance -version: main +version: 5.3.2 nav: - modules/ROOT/nav.adoc asciidoc: attributes: smallrye-fault-tolerance: SmallRye Fault Tolerance - smallrye-fault-tolerance-version: '5.3.1' + smallrye-fault-tolerance-version: '5.3.2' microprofile-fault-tolerance: MicroProfile Fault Tolerance microprofile-fault-tolerance-version: '3.0' From e53c47274b8346b2aa188a018ff2370d25cb33a2 Mon Sep 17 00:00:00 2001 From: SmallRye CI Date: Mon, 14 Feb 2022 16:12:24 +0000 Subject: [PATCH 40/68] [maven-release-plugin] prepare release 5.3.2 --- api/pom.xml | 2 +- implementation/autoconfig/core/pom.xml | 2 +- implementation/autoconfig/pom.xml | 2 +- implementation/autoconfig/processor/pom.xml | 2 +- implementation/context-propagation/pom.xml | 2 +- implementation/core/pom.xml | 2 +- implementation/fault-tolerance/pom.xml | 2 +- implementation/mutiny/pom.xml | 2 +- implementation/pom.xml | 2 +- implementation/rxjava3/pom.xml | 2 +- implementation/standalone/pom.xml | 2 +- implementation/tracing-propagation/pom.xml | 2 +- implementation/vertx/pom.xml | 2 +- pom.xml | 4 ++-- release/pom.xml | 2 +- testsuite/basic/pom.xml | 2 +- testsuite/integration/pom.xml | 2 +- testsuite/pom.xml | 2 +- testsuite/tck/pom.xml | 2 +- 19 files changed, 20 insertions(+), 20 deletions(-) diff --git a/api/pom.xml b/api/pom.xml index 599539b9..7353fb41 100644 --- a/api/pom.xml +++ b/api/pom.xml @@ -20,7 +20,7 @@ io.smallrye smallrye-fault-tolerance-parent - 5.3.2-SNAPSHOT + 5.3.2 smallrye-fault-tolerance-api diff --git a/implementation/autoconfig/core/pom.xml b/implementation/autoconfig/core/pom.xml index 0fd01ab0..95372d88 100644 --- a/implementation/autoconfig/core/pom.xml +++ b/implementation/autoconfig/core/pom.xml @@ -5,7 +5,7 @@ io.smallrye smallrye-fault-tolerance-autoconfig-parent - 5.3.2-SNAPSHOT + 5.3.2 smallrye-fault-tolerance-autoconfig-core diff --git a/implementation/autoconfig/pom.xml b/implementation/autoconfig/pom.xml index 55219b82..d7a23b6b 100644 --- a/implementation/autoconfig/pom.xml +++ b/implementation/autoconfig/pom.xml @@ -5,7 +5,7 @@ io.smallrye smallrye-fault-tolerance-implementation-parent - 5.3.2-SNAPSHOT + 5.3.2 smallrye-fault-tolerance-autoconfig-parent diff --git a/implementation/autoconfig/processor/pom.xml b/implementation/autoconfig/processor/pom.xml index 130750ec..58f85918 100644 --- a/implementation/autoconfig/processor/pom.xml +++ b/implementation/autoconfig/processor/pom.xml @@ -5,7 +5,7 @@ io.smallrye smallrye-fault-tolerance-autoconfig-parent - 5.3.2-SNAPSHOT + 5.3.2 smallrye-fault-tolerance-autoconfig-processor diff --git a/implementation/context-propagation/pom.xml b/implementation/context-propagation/pom.xml index a374f90c..7c3eaaec 100644 --- a/implementation/context-propagation/pom.xml +++ b/implementation/context-propagation/pom.xml @@ -20,7 +20,7 @@ io.smallrye smallrye-fault-tolerance-implementation-parent - 5.3.2-SNAPSHOT + 5.3.2 smallrye-fault-tolerance-context-propagation diff --git a/implementation/core/pom.xml b/implementation/core/pom.xml index 04167556..fc475179 100644 --- a/implementation/core/pom.xml +++ b/implementation/core/pom.xml @@ -20,7 +20,7 @@ io.smallrye smallrye-fault-tolerance-implementation-parent - 5.3.2-SNAPSHOT + 5.3.2 smallrye-fault-tolerance-core diff --git a/implementation/fault-tolerance/pom.xml b/implementation/fault-tolerance/pom.xml index 7bb58bc5..a25a5590 100644 --- a/implementation/fault-tolerance/pom.xml +++ b/implementation/fault-tolerance/pom.xml @@ -20,7 +20,7 @@ io.smallrye smallrye-fault-tolerance-implementation-parent - 5.3.2-SNAPSHOT + 5.3.2 smallrye-fault-tolerance diff --git a/implementation/mutiny/pom.xml b/implementation/mutiny/pom.xml index f8aa7d6c..7b8a5716 100644 --- a/implementation/mutiny/pom.xml +++ b/implementation/mutiny/pom.xml @@ -5,7 +5,7 @@ io.smallrye smallrye-fault-tolerance-implementation-parent - 5.3.2-SNAPSHOT + 5.3.2 smallrye-fault-tolerance-mutiny diff --git a/implementation/pom.xml b/implementation/pom.xml index af999e0c..9d1b4ed8 100644 --- a/implementation/pom.xml +++ b/implementation/pom.xml @@ -20,7 +20,7 @@ io.smallrye smallrye-fault-tolerance-parent - 5.3.2-SNAPSHOT + 5.3.2 smallrye-fault-tolerance-implementation-parent diff --git a/implementation/rxjava3/pom.xml b/implementation/rxjava3/pom.xml index b5588c96..37651a67 100644 --- a/implementation/rxjava3/pom.xml +++ b/implementation/rxjava3/pom.xml @@ -5,7 +5,7 @@ io.smallrye smallrye-fault-tolerance-implementation-parent - 5.3.2-SNAPSHOT + 5.3.2 smallrye-fault-tolerance-rxjava3 diff --git a/implementation/standalone/pom.xml b/implementation/standalone/pom.xml index 4782d3cd..ea6cfa1d 100644 --- a/implementation/standalone/pom.xml +++ b/implementation/standalone/pom.xml @@ -5,7 +5,7 @@ io.smallrye smallrye-fault-tolerance-implementation-parent - 5.3.2-SNAPSHOT + 5.3.2 smallrye-fault-tolerance-standalone diff --git a/implementation/tracing-propagation/pom.xml b/implementation/tracing-propagation/pom.xml index 654fe873..50dcbcd0 100644 --- a/implementation/tracing-propagation/pom.xml +++ b/implementation/tracing-propagation/pom.xml @@ -20,7 +20,7 @@ io.smallrye smallrye-fault-tolerance-implementation-parent - 5.3.2-SNAPSHOT + 5.3.2 smallrye-fault-tolerance-tracing-propagation diff --git a/implementation/vertx/pom.xml b/implementation/vertx/pom.xml index f77a6681..7588dd88 100644 --- a/implementation/vertx/pom.xml +++ b/implementation/vertx/pom.xml @@ -20,7 +20,7 @@ io.smallrye smallrye-fault-tolerance-implementation-parent - 5.3.2-SNAPSHOT + 5.3.2 smallrye-fault-tolerance-vertx diff --git a/pom.xml b/pom.xml index 0ce47959..8f4cf98c 100644 --- a/pom.xml +++ b/pom.xml @@ -24,7 +24,7 @@ smallrye-fault-tolerance-parent - 5.3.2-SNAPSHOT + 5.3.2 pom SmallRye Fault Tolerance: Parent @@ -77,7 +77,7 @@ scm:git:git@github.com:smallrye/smallrye-fault-tolerance.git scm:git:git@github.com:smallrye/smallrye-fault-tolerance.git https://github.com/smallrye/smallrye-fault-tolerance/ - HEAD + 5.3.2 diff --git a/release/pom.xml b/release/pom.xml index 70a4b933..136a1e29 100644 --- a/release/pom.xml +++ b/release/pom.xml @@ -5,7 +5,7 @@ io.smallrye smallrye-fault-tolerance-parent - 5.3.2-SNAPSHOT + 5.3.2 smallrye-fault-tolerance-release diff --git a/testsuite/basic/pom.xml b/testsuite/basic/pom.xml index d1638917..62deae68 100644 --- a/testsuite/basic/pom.xml +++ b/testsuite/basic/pom.xml @@ -20,7 +20,7 @@ smallrye-fault-tolerance-testsuite-parent io.smallrye - 5.3.2-SNAPSHOT + 5.3.2 smallrye-fault-tolerance-testsuite-basic diff --git a/testsuite/integration/pom.xml b/testsuite/integration/pom.xml index 60488060..7bd9c89b 100644 --- a/testsuite/integration/pom.xml +++ b/testsuite/integration/pom.xml @@ -5,7 +5,7 @@ smallrye-fault-tolerance-testsuite-parent io.smallrye - 5.3.2-SNAPSHOT + 5.3.2 smallrye-fault-tolerance-testsuite-integration diff --git a/testsuite/pom.xml b/testsuite/pom.xml index a8c418b5..6d8aec79 100644 --- a/testsuite/pom.xml +++ b/testsuite/pom.xml @@ -5,7 +5,7 @@ smallrye-fault-tolerance-parent io.smallrye - 5.3.2-SNAPSHOT + 5.3.2 smallrye-fault-tolerance-testsuite-parent diff --git a/testsuite/tck/pom.xml b/testsuite/tck/pom.xml index 4d816259..ed8865d2 100644 --- a/testsuite/tck/pom.xml +++ b/testsuite/tck/pom.xml @@ -5,7 +5,7 @@ io.smallrye smallrye-fault-tolerance-testsuite-parent - 5.3.2-SNAPSHOT + 5.3.2 smallrye-fault-tolerance-tck From d80515cef0e1a25294baff64ed08d446744b8335 Mon Sep 17 00:00:00 2001 From: SmallRye CI Date: Mon, 14 Feb 2022 16:12:24 +0000 Subject: [PATCH 41/68] [maven-release-plugin] prepare for next development iteration --- api/pom.xml | 2 +- implementation/autoconfig/core/pom.xml | 2 +- implementation/autoconfig/pom.xml | 2 +- implementation/autoconfig/processor/pom.xml | 2 +- implementation/context-propagation/pom.xml | 2 +- implementation/core/pom.xml | 2 +- implementation/fault-tolerance/pom.xml | 2 +- implementation/mutiny/pom.xml | 2 +- implementation/pom.xml | 2 +- implementation/rxjava3/pom.xml | 2 +- implementation/standalone/pom.xml | 2 +- implementation/tracing-propagation/pom.xml | 2 +- implementation/vertx/pom.xml | 2 +- pom.xml | 4 ++-- release/pom.xml | 2 +- testsuite/basic/pom.xml | 2 +- testsuite/integration/pom.xml | 2 +- testsuite/pom.xml | 2 +- testsuite/tck/pom.xml | 2 +- 19 files changed, 20 insertions(+), 20 deletions(-) diff --git a/api/pom.xml b/api/pom.xml index 7353fb41..7b31079b 100644 --- a/api/pom.xml +++ b/api/pom.xml @@ -20,7 +20,7 @@ io.smallrye smallrye-fault-tolerance-parent - 5.3.2 + 5.3.3-SNAPSHOT smallrye-fault-tolerance-api diff --git a/implementation/autoconfig/core/pom.xml b/implementation/autoconfig/core/pom.xml index 95372d88..3a34f295 100644 --- a/implementation/autoconfig/core/pom.xml +++ b/implementation/autoconfig/core/pom.xml @@ -5,7 +5,7 @@ io.smallrye smallrye-fault-tolerance-autoconfig-parent - 5.3.2 + 5.3.3-SNAPSHOT smallrye-fault-tolerance-autoconfig-core diff --git a/implementation/autoconfig/pom.xml b/implementation/autoconfig/pom.xml index d7a23b6b..f301f85b 100644 --- a/implementation/autoconfig/pom.xml +++ b/implementation/autoconfig/pom.xml @@ -5,7 +5,7 @@ io.smallrye smallrye-fault-tolerance-implementation-parent - 5.3.2 + 5.3.3-SNAPSHOT smallrye-fault-tolerance-autoconfig-parent diff --git a/implementation/autoconfig/processor/pom.xml b/implementation/autoconfig/processor/pom.xml index 58f85918..29cfbb0e 100644 --- a/implementation/autoconfig/processor/pom.xml +++ b/implementation/autoconfig/processor/pom.xml @@ -5,7 +5,7 @@ io.smallrye smallrye-fault-tolerance-autoconfig-parent - 5.3.2 + 5.3.3-SNAPSHOT smallrye-fault-tolerance-autoconfig-processor diff --git a/implementation/context-propagation/pom.xml b/implementation/context-propagation/pom.xml index 7c3eaaec..60156c2d 100644 --- a/implementation/context-propagation/pom.xml +++ b/implementation/context-propagation/pom.xml @@ -20,7 +20,7 @@ io.smallrye smallrye-fault-tolerance-implementation-parent - 5.3.2 + 5.3.3-SNAPSHOT smallrye-fault-tolerance-context-propagation diff --git a/implementation/core/pom.xml b/implementation/core/pom.xml index fc475179..fceb2b78 100644 --- a/implementation/core/pom.xml +++ b/implementation/core/pom.xml @@ -20,7 +20,7 @@ io.smallrye smallrye-fault-tolerance-implementation-parent - 5.3.2 + 5.3.3-SNAPSHOT smallrye-fault-tolerance-core diff --git a/implementation/fault-tolerance/pom.xml b/implementation/fault-tolerance/pom.xml index a25a5590..9d1b7c78 100644 --- a/implementation/fault-tolerance/pom.xml +++ b/implementation/fault-tolerance/pom.xml @@ -20,7 +20,7 @@ io.smallrye smallrye-fault-tolerance-implementation-parent - 5.3.2 + 5.3.3-SNAPSHOT smallrye-fault-tolerance diff --git a/implementation/mutiny/pom.xml b/implementation/mutiny/pom.xml index 7b8a5716..74bec8f0 100644 --- a/implementation/mutiny/pom.xml +++ b/implementation/mutiny/pom.xml @@ -5,7 +5,7 @@ io.smallrye smallrye-fault-tolerance-implementation-parent - 5.3.2 + 5.3.3-SNAPSHOT smallrye-fault-tolerance-mutiny diff --git a/implementation/pom.xml b/implementation/pom.xml index 9d1b4ed8..6f5369e1 100644 --- a/implementation/pom.xml +++ b/implementation/pom.xml @@ -20,7 +20,7 @@ io.smallrye smallrye-fault-tolerance-parent - 5.3.2 + 5.3.3-SNAPSHOT smallrye-fault-tolerance-implementation-parent diff --git a/implementation/rxjava3/pom.xml b/implementation/rxjava3/pom.xml index 37651a67..39e378cb 100644 --- a/implementation/rxjava3/pom.xml +++ b/implementation/rxjava3/pom.xml @@ -5,7 +5,7 @@ io.smallrye smallrye-fault-tolerance-implementation-parent - 5.3.2 + 5.3.3-SNAPSHOT smallrye-fault-tolerance-rxjava3 diff --git a/implementation/standalone/pom.xml b/implementation/standalone/pom.xml index ea6cfa1d..6fad48a0 100644 --- a/implementation/standalone/pom.xml +++ b/implementation/standalone/pom.xml @@ -5,7 +5,7 @@ io.smallrye smallrye-fault-tolerance-implementation-parent - 5.3.2 + 5.3.3-SNAPSHOT smallrye-fault-tolerance-standalone diff --git a/implementation/tracing-propagation/pom.xml b/implementation/tracing-propagation/pom.xml index 50dcbcd0..f6efa95c 100644 --- a/implementation/tracing-propagation/pom.xml +++ b/implementation/tracing-propagation/pom.xml @@ -20,7 +20,7 @@ io.smallrye smallrye-fault-tolerance-implementation-parent - 5.3.2 + 5.3.3-SNAPSHOT smallrye-fault-tolerance-tracing-propagation diff --git a/implementation/vertx/pom.xml b/implementation/vertx/pom.xml index 7588dd88..3637c0e4 100644 --- a/implementation/vertx/pom.xml +++ b/implementation/vertx/pom.xml @@ -20,7 +20,7 @@ io.smallrye smallrye-fault-tolerance-implementation-parent - 5.3.2 + 5.3.3-SNAPSHOT smallrye-fault-tolerance-vertx diff --git a/pom.xml b/pom.xml index 8f4cf98c..531eb7f2 100644 --- a/pom.xml +++ b/pom.xml @@ -24,7 +24,7 @@ smallrye-fault-tolerance-parent - 5.3.2 + 5.3.3-SNAPSHOT pom SmallRye Fault Tolerance: Parent @@ -77,7 +77,7 @@ scm:git:git@github.com:smallrye/smallrye-fault-tolerance.git scm:git:git@github.com:smallrye/smallrye-fault-tolerance.git https://github.com/smallrye/smallrye-fault-tolerance/ - 5.3.2 + HEAD diff --git a/release/pom.xml b/release/pom.xml index 136a1e29..8dcf4e26 100644 --- a/release/pom.xml +++ b/release/pom.xml @@ -5,7 +5,7 @@ io.smallrye smallrye-fault-tolerance-parent - 5.3.2 + 5.3.3-SNAPSHOT smallrye-fault-tolerance-release diff --git a/testsuite/basic/pom.xml b/testsuite/basic/pom.xml index 62deae68..7f57cca3 100644 --- a/testsuite/basic/pom.xml +++ b/testsuite/basic/pom.xml @@ -20,7 +20,7 @@ smallrye-fault-tolerance-testsuite-parent io.smallrye - 5.3.2 + 5.3.3-SNAPSHOT smallrye-fault-tolerance-testsuite-basic diff --git a/testsuite/integration/pom.xml b/testsuite/integration/pom.xml index 7bd9c89b..c2f94d88 100644 --- a/testsuite/integration/pom.xml +++ b/testsuite/integration/pom.xml @@ -5,7 +5,7 @@ smallrye-fault-tolerance-testsuite-parent io.smallrye - 5.3.2 + 5.3.3-SNAPSHOT smallrye-fault-tolerance-testsuite-integration diff --git a/testsuite/pom.xml b/testsuite/pom.xml index 6d8aec79..bf703859 100644 --- a/testsuite/pom.xml +++ b/testsuite/pom.xml @@ -5,7 +5,7 @@ smallrye-fault-tolerance-parent io.smallrye - 5.3.2 + 5.3.3-SNAPSHOT smallrye-fault-tolerance-testsuite-parent diff --git a/testsuite/tck/pom.xml b/testsuite/tck/pom.xml index ed8865d2..544acf9c 100644 --- a/testsuite/tck/pom.xml +++ b/testsuite/tck/pom.xml @@ -5,7 +5,7 @@ io.smallrye smallrye-fault-tolerance-testsuite-parent - 5.3.2 + 5.3.3-SNAPSHOT smallrye-fault-tolerance-tck From cd0f039efb955ed10bec924f00e640d7946e32b5 Mon Sep 17 00:00:00 2001 From: SmallRye CI Date: Mon, 14 Feb 2022 16:16:39 +0000 Subject: [PATCH 42/68] Update README.adoc and antora.yml after release --- README.adoc | 2 +- doc/antora.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.adoc b/README.adoc index 531cd513..c5cc315c 100644 --- a/README.adoc +++ b/README.adoc @@ -9,4 +9,4 @@ image:https://img.shields.io/maven-central/v/io.smallrye/smallrye-fault-toleranc == SmallRye Fault Tolerance SmallRye Fault Tolerance is an implementation of https://github.com/eclipse/microprofile-fault-tolerance/[Eclipse MicroProfile Fault Tolerance]. -See the https://smallrye.io/docs/smallrye-fault-tolerance/5.3.1/index.html[documentation]. +See the https://smallrye.io/docs/smallrye-fault-tolerance/5.3.2/index.html[documentation]. diff --git a/doc/antora.yml b/doc/antora.yml index 003ec02c..cb025901 100644 --- a/doc/antora.yml +++ b/doc/antora.yml @@ -1,6 +1,6 @@ name: smallrye-fault-tolerance title: SmallRye Fault Tolerance -version: 5.3.2 +version: main nav: - modules/ROOT/nav.adoc From c782e2b644d8a1b8b6b146ef09ee6ed4800fe7f3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 15 Feb 2022 07:12:10 +0000 Subject: [PATCH 43/68] Bump smallrye-common-annotation from 1.9.0 to 1.10.0 Bumps [smallrye-common-annotation](https://github.com/smallrye/smallrye-common) from 1.9.0 to 1.10.0. - [Release notes](https://github.com/smallrye/smallrye-common/releases) - [Commits](https://github.com/smallrye/smallrye-common/compare/1.9.0...1.10.0) --- updated-dependencies: - dependency-name: io.smallrye.common:smallrye-common-annotation dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 531eb7f2..d7fe8884 100644 --- a/pom.xml +++ b/pom.xml @@ -47,7 +47,7 @@ 3.0.4 1.2.2 - 1.9.0 + 1.10.0 2.6.0 0.33.0 4.2.4 From 373ef2ed9eff86fcfc9324c27fd54c7f8b7c3948 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 28 Feb 2022 14:20:25 +0000 Subject: [PATCH 44/68] Bump smallrye-build-parent from 34 to 35 Bumps [smallrye-build-parent](https://github.com/smallrye/smallrye-parent) from 34 to 35. - [Release notes](https://github.com/smallrye/smallrye-parent/releases) - [Commits](https://github.com/smallrye/smallrye-parent/commits) --- updated-dependencies: - dependency-name: io.smallrye:smallrye-build-parent dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index d7fe8884..44ccf3a7 100644 --- a/pom.xml +++ b/pom.xml @@ -20,7 +20,7 @@ io.smallrye smallrye-build-parent - 34 + 35 smallrye-fault-tolerance-parent From 0c8976be62d5711a319d900551e17ea445d69f1e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 28 Feb 2022 14:38:50 +0000 Subject: [PATCH 45/68] Bump smallrye-config from 2.9.0 to 2.9.1 Bumps [smallrye-config](https://github.com/smallrye/smallrye-config) from 2.9.0 to 2.9.1. - [Release notes](https://github.com/smallrye/smallrye-config/releases) - [Commits](https://github.com/smallrye/smallrye-config/compare/2.9.0...2.9.1) --- updated-dependencies: - dependency-name: io.smallrye.config:smallrye-config dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 44ccf3a7..0cc3a1e0 100644 --- a/pom.xml +++ b/pom.xml @@ -42,7 +42,7 @@ 3.0 1.2 - 2.9.0 + 2.9.1 3.0.4 From df64614783a2c3f933a49cf6d9699e22e7dbb45e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 28 Feb 2022 15:56:06 +0000 Subject: [PATCH 46/68] Bump vertx-core from 4.2.4 to 4.2.5 Bumps [vertx-core](https://github.com/eclipse/vert.x) from 4.2.4 to 4.2.5. - [Release notes](https://github.com/eclipse/vert.x/releases) - [Commits](https://github.com/eclipse/vert.x/compare/4.2.4...4.2.5) --- updated-dependencies: - dependency-name: io.vertx:vertx-core dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 0cc3a1e0..bc1d00bd 100644 --- a/pom.xml +++ b/pom.xml @@ -50,7 +50,7 @@ 1.10.0 2.6.0 0.33.0 - 4.2.4 + 4.2.5 1.6.0.Final 2.1.0.Final From 9d5b6ddd7d096c2669ba1ffc7e9676b9796a5395 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 28 Feb 2022 16:36:49 +0000 Subject: [PATCH 47/68] Bump junit-pioneer from 1.5.0 to 1.6.1 Bumps [junit-pioneer](https://github.com/junit-pioneer/junit-pioneer) from 1.5.0 to 1.6.1. - [Release notes](https://github.com/junit-pioneer/junit-pioneer/releases) - [Commits](https://github.com/junit-pioneer/junit-pioneer/compare/v1.5.0...v1.6.1) --- updated-dependencies: - dependency-name: org.junit-pioneer:junit-pioneer dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index bc1d00bd..1573b8a5 100644 --- a/pom.xml +++ b/pom.xml @@ -57,7 +57,7 @@ 3.22.0 4.1.1 5.8.2 - 1.5.0 + 1.6.1 1.3.1 3.1.3 6.14.3 From b7d33e76820c316f978fc86b1e35db308637fc3a Mon Sep 17 00:00:00 2001 From: Ladislav Thon Date: Tue, 1 Mar 2022 13:04:55 +0100 Subject: [PATCH 48/68] add when methods for allowing programmatic exception decisions The `when` method is added to builders for circuit breaker, fallback and retry and can't be combined with the existing methods that allow declarative exception decisions (based on sets of exception types). --- .../faulttolerance/api/FaultTolerance.java | 40 +++++++++ .../core/apiimpl/FaultToleranceImpl.java | 75 +++++++++++++--- .../core/util/ExceptionDecision.java | 72 +--------------- .../util/PredicateBasedExceptionDecision.java | 16 ++++ .../core/util/SetBasedExceptionDecision.java | 65 ++++++++++++++ .../circuit/breaker/CircuitBreakerTest.java | 6 +- .../CompletionStageCircuitBreakerTest.java | 8 +- .../breaker/FutureCircuitBreakerTest.java | 6 +- .../core/retry/CompletionStageRetryTest.java | 13 +-- .../core/retry/FutureRetryTest.java | 17 ++-- .../faulttolerance/core/retry/RetryTest.java | 85 ++++++++++--------- .../core/util/ExceptionDecisionTest.java | 45 ---------- .../PredicateBasedExceptionDecisionTest.java | 33 +++++++ .../util/SetBasedExceptionDecisionTest.java | 48 +++++++++++ .../FaultToleranceInterceptor.java | 3 +- .../mutiny/test/MutinyCircuitBreakerTest.java | 20 +++++ .../mutiny/test/MutinyFallbackTest.java | 12 +++ .../mutiny/test/MutinyRetryTest.java | 13 +++ .../StandaloneCircuitBreakerAsyncTest.java | 26 +++++- .../test/StandaloneCircuitBreakerTest.java | 14 +++ .../test/StandaloneFallbackAsyncTest.java | 12 +++ .../test/StandaloneFallbackTest.java | 9 ++ .../test/StandaloneRetryAsyncTest.java | 13 +++ .../standalone/test/StandaloneRetryTest.java | 11 +++ 24 files changed, 469 insertions(+), 193 deletions(-) create mode 100644 implementation/core/src/main/java/io/smallrye/faulttolerance/core/util/PredicateBasedExceptionDecision.java create mode 100644 implementation/core/src/main/java/io/smallrye/faulttolerance/core/util/SetBasedExceptionDecision.java delete mode 100644 implementation/core/src/test/java/io/smallrye/faulttolerance/core/util/ExceptionDecisionTest.java create mode 100644 implementation/core/src/test/java/io/smallrye/faulttolerance/core/util/PredicateBasedExceptionDecisionTest.java create mode 100644 implementation/core/src/test/java/io/smallrye/faulttolerance/core/util/SetBasedExceptionDecisionTest.java 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 a9e68218..b9cd7b9c 100644 --- a/api/src/main/java/io/smallrye/faulttolerance/api/FaultTolerance.java +++ b/api/src/main/java/io/smallrye/faulttolerance/api/FaultTolerance.java @@ -9,6 +9,7 @@ import java.util.concurrent.Future; import java.util.function.Consumer; import java.util.function.Function; +import java.util.function.Predicate; import java.util.function.Supplier; import org.eclipse.microprofile.faulttolerance.Asynchronous; @@ -446,6 +447,19 @@ 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. * @@ -617,6 +631,19 @@ 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. * @@ -717,6 +744,19 @@ default RetryBuilder abortOn(Class value) { return abortOn(Collections.singleton(Objects.requireNonNull(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 fallback builder + */ + RetryBuilder when(Predicate value); + /** * Configures retry to use an exponential backoff instead of the default constant backoff. *

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 6dcf35bb..035e67a6 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 @@ -14,6 +14,7 @@ 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; @@ -54,6 +55,8 @@ import io.smallrye.faulttolerance.core.util.ExceptionDecision; import io.smallrye.faulttolerance.core.util.Initializer; import io.smallrye.faulttolerance.core.util.Preconditions; +import io.smallrye.faulttolerance.core.util.PredicateBasedExceptionDecision; +import io.smallrye.faulttolerance.core.util.SetBasedExceptionDecision; import io.smallrye.faulttolerance.core.util.SetOfThrowables; public final class FaultToleranceImpl implements FaultTolerance { @@ -210,7 +213,8 @@ private FaultToleranceStrategy buildSyncStrategy(List initActions) if (ftEnabled && circuitBreakerBuilder != null) { result = new CircuitBreaker<>(result, description, - createExceptionDecision(circuitBreakerBuilder.skipOn, circuitBreakerBuilder.failOn), + createExceptionDecision(circuitBreakerBuilder.skipOn, circuitBreakerBuilder.failOn, + circuitBreakerBuilder.whenPredicate), circuitBreakerBuilder.delayInMillis, circuitBreakerBuilder.requestVolumeThreshold, circuitBreakerBuilder.failureRatio, @@ -231,8 +235,8 @@ private FaultToleranceStrategy buildSyncStrategy(List initActions) Supplier backoff = prepareRetryBackoff(retryBuilder); result = new Retry<>(result, description, - createExceptionDecision(retryBuilder.abortOn, retryBuilder.retryOn), retryBuilder.maxRetries, - retryBuilder.maxDurationInMillis, () -> new ThreadSleepDelay(backoff.get()), + createExceptionDecision(retryBuilder.abortOn, retryBuilder.retryOn, retryBuilder.whenPredicate), + retryBuilder.maxRetries, retryBuilder.maxDurationInMillis, () -> new ThreadSleepDelay(backoff.get()), new SystemStopwatch()); } @@ -240,7 +244,8 @@ private FaultToleranceStrategy buildSyncStrategy(List initActions) if (fallbackBuilder != null) { FallbackFunction fallbackFunction = ctx -> fallbackBuilder.handler.apply(ctx.failure); result = new Fallback<>(result, description, fallbackFunction, - createExceptionDecision(fallbackBuilder.skipOn, fallbackBuilder.applyOn)); + createExceptionDecision(fallbackBuilder.skipOn, fallbackBuilder.applyOn, + fallbackBuilder.whenPredicate)); } return result; @@ -283,7 +288,8 @@ private FaultToleranceStrategy> buildCompletionStageChain( if (ftEnabled && circuitBreakerBuilder != null) { result = new CompletionStageCircuitBreaker<>(result, description, - createExceptionDecision(circuitBreakerBuilder.skipOn, circuitBreakerBuilder.failOn), + createExceptionDecision(circuitBreakerBuilder.skipOn, circuitBreakerBuilder.failOn, + circuitBreakerBuilder.whenPredicate), circuitBreakerBuilder.delayInMillis, circuitBreakerBuilder.requestVolumeThreshold, circuitBreakerBuilder.failureRatio, @@ -304,8 +310,8 @@ private FaultToleranceStrategy> buildCompletionStageChain( Supplier backoff = prepareRetryBackoff(retryBuilder); result = new CompletionStageRetry<>(result, description, - createExceptionDecision(retryBuilder.abortOn, retryBuilder.retryOn), retryBuilder.maxRetries, - retryBuilder.maxDurationInMillis, () -> new TimerDelay(backoff.get(), timer), + createExceptionDecision(retryBuilder.abortOn, retryBuilder.retryOn, retryBuilder.whenPredicate), + retryBuilder.maxRetries, retryBuilder.maxDurationInMillis, () -> new TimerDelay(backoff.get(), timer), new SystemStopwatch()); } @@ -314,7 +320,8 @@ private FaultToleranceStrategy> buildCompletionStageChain( FallbackFunction> fallbackFunction = ctx -> AsyncTypes.toCompletionStageIfRequired( fallbackBuilder.handler.apply(ctx.failure), asyncType); result = new CompletionStageFallback<>(result, description, fallbackFunction, - createExceptionDecision(fallbackBuilder.skipOn, fallbackBuilder.applyOn)); + createExceptionDecision(fallbackBuilder.skipOn, fallbackBuilder.applyOn, + fallbackBuilder.whenPredicate)); } // thread offload is always enabled @@ -330,8 +337,14 @@ private static long getTimeInMs(long time, ChronoUnit unit) { } private static ExceptionDecision createExceptionDecision(Collection> consideredExpected, - Collection> consideredFailure) { - return new ExceptionDecision(createSetOfThrowables(consideredFailure), + Collection> consideredFailure, Predicate whenPredicate) { + if (whenPredicate != 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(whenPredicate.negate()); + } + return new SetBasedExceptionDecision(createSetOfThrowables(consideredFailure), createSetOfThrowables(consideredExpected), true); } @@ -422,6 +435,8 @@ static class CircuitBreakerBuilderImpl implements CircuitBreakerBuilder> 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; @@ -441,12 +456,20 @@ static class CircuitBreakerBuilderImpl implements 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; } @@ -509,6 +532,10 @@ public CircuitBreakerBuilder onPrevented(Runnable callback) { @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; } @@ -520,6 +547,8 @@ static class FallbackBuilderImpl implements FallbackBuilder { 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; @@ -541,12 +570,20 @@ public FallbackBuilder handler(Function value) { @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; } @@ -554,6 +591,10 @@ public FallbackBuilder skipOn(Collection> value 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; } @@ -568,6 +609,8 @@ static class RetryBuilderImpl implements RetryBuilder { private long jitterInMillis = 200; private Collection> retryOn = Collections.singleton(Exception.class); private Collection> abortOn = Collections.emptySet(); + private boolean setBasedExceptionDecisionDefined = false; + private Predicate whenPredicate; private ExponentialBackoffBuilderImpl exponentialBackoffBuilder; private FibonacciBackoffBuilderImpl fibonacciBackoffBuilder; @@ -617,12 +660,20 @@ public RetryBuilder jitter(long value, ChronoUnit unit) { @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 when(Predicate value) { + this.whenPredicate = Preconditions.checkNotNull(value, "Exception predicate must be set"); return this; } @@ -661,6 +712,10 @@ public RetryBuilder onFailure(Runnable callback) { @Override public Builder done() { + if (whenPredicate != null && setBasedExceptionDecisionDefined) { + throw new IllegalStateException("The when() method may not be combined with retryOn() / abortOn()"); + } + int backoffStrategies = 0; if (exponentialBackoffBuilder != null) { backoffStrategies++; diff --git a/implementation/core/src/main/java/io/smallrye/faulttolerance/core/util/ExceptionDecision.java b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/util/ExceptionDecision.java index ccf4b4a4..8f8285f4 100644 --- a/implementation/core/src/main/java/io/smallrye/faulttolerance/core/util/ExceptionDecision.java +++ b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/util/ExceptionDecision.java @@ -1,72 +1,8 @@ package io.smallrye.faulttolerance.core.util; -import static io.smallrye.faulttolerance.core.util.Preconditions.checkNotNull; +public interface ExceptionDecision { + boolean isConsideredExpected(Throwable e); -import java.util.Collections; -import java.util.IdentityHashMap; -import java.util.Set; - -public class ExceptionDecision { - public static final ExceptionDecision ALWAYS_FAILURE = new ExceptionDecision(SetOfThrowables.ALL, - SetOfThrowables.EMPTY, false); - public static final ExceptionDecision ALWAYS_EXPECTED = new ExceptionDecision(SetOfThrowables.EMPTY, - SetOfThrowables.ALL, false); - - public static final ExceptionDecision EMPTY = new ExceptionDecision(SetOfThrowables.EMPTY, - SetOfThrowables.EMPTY, false); - - // @CircuitBreaker.failOn, @Fallback.applyOn, @Retry.retryOn - private final SetOfThrowables consideredFailure; - // @CircuitBreaker.skipOn, @Fallback.skipOn, @Retry.abortOn - private final SetOfThrowables consideredExpected; - - private final boolean inspectCauseChain; - - public ExceptionDecision(SetOfThrowables consideredFailure, SetOfThrowables consideredExpected, boolean inspectCauseChain) { - this.consideredFailure = checkNotNull(consideredFailure, "Set of considered-failure throwables must be set"); - this.consideredExpected = checkNotNull(consideredExpected, "Set of considered-expected throwables must be set"); - this.inspectCauseChain = inspectCauseChain; - } - - public boolean isConsideredExpected(Throwable e) { - // per `@CircuitBreaker` javadoc, `skipOn` takes priority over `failOn` - // per `@Fallback` javadoc, `skipOn` takes priority over `applyOn` - // per `@Retry` javadoc, `abortOn` takes priority over `retryOn` - // to sum up, the exceptions considered expected win over those considered failure - - if (consideredExpected.includes(e.getClass())) { - return true; - } - if (consideredFailure.includes(e.getClass())) { - return false; - } - if (!inspectCauseChain) { - return true; - } - - if (includes(consideredExpected, e)) { - return true; - } - if (includes(consideredFailure, e)) { - return false; - } - return true; - } - - private boolean includes(SetOfThrowables set, Throwable e) { - Set alreadySeen = Collections.newSetFromMap(new IdentityHashMap<>()); - - // guard against hypothetical cycle in the cause chain - while (e != null && !alreadySeen.contains(e)) { - alreadySeen.add(e); - - if (set.includes(e.getClass())) { - return true; - } - - e = e.getCause(); - } - - return false; - } + ExceptionDecision ALWAYS_EXPECTED = ignored -> true; + ExceptionDecision ALWAYS_FAILURE = ignored -> false; } diff --git a/implementation/core/src/main/java/io/smallrye/faulttolerance/core/util/PredicateBasedExceptionDecision.java b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/util/PredicateBasedExceptionDecision.java new file mode 100644 index 00000000..e77b9ab2 --- /dev/null +++ b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/util/PredicateBasedExceptionDecision.java @@ -0,0 +1,16 @@ +package io.smallrye.faulttolerance.core.util; + +import java.util.function.Predicate; + +public class PredicateBasedExceptionDecision implements ExceptionDecision { + private final Predicate isExpected; + + public PredicateBasedExceptionDecision(Predicate isExpected) { + this.isExpected = isExpected; + } + + @Override + public boolean isConsideredExpected(Throwable e) { + return isExpected.test(e); + } +} diff --git a/implementation/core/src/main/java/io/smallrye/faulttolerance/core/util/SetBasedExceptionDecision.java b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/util/SetBasedExceptionDecision.java new file mode 100644 index 00000000..44a054fe --- /dev/null +++ b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/util/SetBasedExceptionDecision.java @@ -0,0 +1,65 @@ +package io.smallrye.faulttolerance.core.util; + +import static io.smallrye.faulttolerance.core.util.Preconditions.checkNotNull; + +import java.util.Collections; +import java.util.IdentityHashMap; +import java.util.Set; + +public class SetBasedExceptionDecision implements ExceptionDecision { + // @CircuitBreaker.failOn, @Fallback.applyOn, @Retry.retryOn + private final SetOfThrowables consideredFailure; + // @CircuitBreaker.skipOn, @Fallback.skipOn, @Retry.abortOn + private final SetOfThrowables consideredExpected; + + private final boolean inspectCauseChain; + + public SetBasedExceptionDecision(SetOfThrowables consideredFailure, SetOfThrowables consideredExpected, + boolean inspectCauseChain) { + this.consideredFailure = checkNotNull(consideredFailure, "Set of considered-failure throwables must be set"); + this.consideredExpected = checkNotNull(consideredExpected, "Set of considered-expected throwables must be set"); + this.inspectCauseChain = inspectCauseChain; + } + + public boolean isConsideredExpected(Throwable e) { + // per `@CircuitBreaker` javadoc, `skipOn` takes priority over `failOn` + // per `@Fallback` javadoc, `skipOn` takes priority over `applyOn` + // per `@Retry` javadoc, `abortOn` takes priority over `retryOn` + // to sum up, the exceptions considered expected win over those considered failure + + if (consideredExpected.includes(e.getClass())) { + return true; + } + if (consideredFailure.includes(e.getClass())) { + return false; + } + if (!inspectCauseChain) { + return true; + } + + if (includes(consideredExpected, e)) { + return true; + } + if (includes(consideredFailure, e)) { + return false; + } + return true; + } + + private boolean includes(SetOfThrowables set, Throwable e) { + Set alreadySeen = Collections.newSetFromMap(new IdentityHashMap<>()); + + // guard against hypothetical cycle in the cause chain + while (e != null && !alreadySeen.contains(e)) { + alreadySeen.add(e); + + if (set.includes(e.getClass())) { + return true; + } + + e = e.getCause(); + } + + return false; + } +} diff --git a/implementation/core/src/test/java/io/smallrye/faulttolerance/core/circuit/breaker/CircuitBreakerTest.java b/implementation/core/src/test/java/io/smallrye/faulttolerance/core/circuit/breaker/CircuitBreakerTest.java index e33c9fed..0e95cac0 100644 --- a/implementation/core/src/test/java/io/smallrye/faulttolerance/core/circuit/breaker/CircuitBreakerTest.java +++ b/implementation/core/src/test/java/io/smallrye/faulttolerance/core/circuit/breaker/CircuitBreakerTest.java @@ -10,7 +10,7 @@ import io.smallrye.faulttolerance.core.InvocationContext; import io.smallrye.faulttolerance.core.stopwatch.TestStopwatch; -import io.smallrye.faulttolerance.core.util.ExceptionDecision; +import io.smallrye.faulttolerance.core.util.SetBasedExceptionDecision; import io.smallrye.faulttolerance.core.util.SetOfThrowables; import io.smallrye.faulttolerance.core.util.TestException; @@ -27,7 +27,7 @@ public void setUp() { @Test public void test1() throws Exception { CircuitBreaker cb = new CircuitBreaker<>(invocation(), "test invocation", - new ExceptionDecision(testException, SetOfThrowables.EMPTY, false), + new SetBasedExceptionDecision(testException, SetOfThrowables.EMPTY, false), 1000, 4, 0.5, 2, stopwatch); // circuit breaker is closed @@ -79,7 +79,7 @@ public void test1() throws Exception { @Test public void test2() throws Exception { CircuitBreaker cb = new CircuitBreaker<>(invocation(), "test invocation", - new ExceptionDecision(testException, SetOfThrowables.EMPTY, false), + new SetBasedExceptionDecision(testException, SetOfThrowables.EMPTY, false), 1000, 4, 0.5, 2, stopwatch); // circuit breaker is closed diff --git a/implementation/core/src/test/java/io/smallrye/faulttolerance/core/circuit/breaker/CompletionStageCircuitBreakerTest.java b/implementation/core/src/test/java/io/smallrye/faulttolerance/core/circuit/breaker/CompletionStageCircuitBreakerTest.java index e555b5ff..23d0ee51 100644 --- a/implementation/core/src/test/java/io/smallrye/faulttolerance/core/circuit/breaker/CompletionStageCircuitBreakerTest.java +++ b/implementation/core/src/test/java/io/smallrye/faulttolerance/core/circuit/breaker/CompletionStageCircuitBreakerTest.java @@ -20,7 +20,7 @@ import io.smallrye.faulttolerance.core.InvocationContext; import io.smallrye.faulttolerance.core.async.CompletionStageExecution; import io.smallrye.faulttolerance.core.stopwatch.TestStopwatch; -import io.smallrye.faulttolerance.core.util.ExceptionDecision; +import io.smallrye.faulttolerance.core.util.SetBasedExceptionDecision; import io.smallrye.faulttolerance.core.util.SetOfThrowables; import io.smallrye.faulttolerance.core.util.TestException; @@ -48,7 +48,7 @@ public void tearDown() throws InterruptedException { public void test1() throws Exception { CompletionStageExecution execution = new CompletionStageExecution<>(invocation(), executor); CompletionStageCircuitBreaker cb = new CompletionStageCircuitBreaker<>(execution, "test invocation", - new ExceptionDecision(testException, SetOfThrowables.EMPTY, false), + new SetBasedExceptionDecision(testException, SetOfThrowables.EMPTY, false), 1000, 4, 0.5, 2, stopwatch); // circuit breaker is closed @@ -101,7 +101,7 @@ public void test1() throws Exception { public void test2() throws Exception { CompletionStageExecution execution = new CompletionStageExecution<>(invocation(), executor); CompletionStageCircuitBreaker cb = new CompletionStageCircuitBreaker<>(execution, "test invocation", - new ExceptionDecision(testException, SetOfThrowables.EMPTY, false), + new SetBasedExceptionDecision(testException, SetOfThrowables.EMPTY, false), 1000, 4, 0.5, 2, stopwatch); // circuit breaker is closed @@ -136,7 +136,7 @@ public void shouldTreatCompletionStageFailureAsCBFailure() throws Exception { CompletionStageExecution execution = new CompletionStageExecution<>(invocation(), executor); CompletionStageCircuitBreaker cb = new CompletionStageCircuitBreaker<>(execution, "test invocation", - new ExceptionDecision(testException, SetOfThrowables.EMPTY, false), + new SetBasedExceptionDecision(testException, SetOfThrowables.EMPTY, false), 1000, 4, 0.5, 2, stopwatch); assertThatThrownBy(cb.apply(eventuallyFailingWith(exception)).toCompletableFuture()::get) diff --git a/implementation/core/src/test/java/io/smallrye/faulttolerance/core/circuit/breaker/FutureCircuitBreakerTest.java b/implementation/core/src/test/java/io/smallrye/faulttolerance/core/circuit/breaker/FutureCircuitBreakerTest.java index 23b80658..550a1f3e 100644 --- a/implementation/core/src/test/java/io/smallrye/faulttolerance/core/circuit/breaker/FutureCircuitBreakerTest.java +++ b/implementation/core/src/test/java/io/smallrye/faulttolerance/core/circuit/breaker/FutureCircuitBreakerTest.java @@ -13,7 +13,7 @@ import io.smallrye.faulttolerance.core.InvocationContext; import io.smallrye.faulttolerance.core.stopwatch.TestStopwatch; -import io.smallrye.faulttolerance.core.util.ExceptionDecision; +import io.smallrye.faulttolerance.core.util.SetBasedExceptionDecision; import io.smallrye.faulttolerance.core.util.SetOfThrowables; import io.smallrye.faulttolerance.core.util.TestException; @@ -30,7 +30,7 @@ public void setUp() { @Test public void test1() throws Exception { CircuitBreaker> cb = new CircuitBreaker<>(invocation(), "test invocation", - new ExceptionDecision(testException, SetOfThrowables.EMPTY, false), + new SetBasedExceptionDecision(testException, SetOfThrowables.EMPTY, false), 1000, 4, 0.5, 2, stopwatch); // circuit breaker is closed @@ -87,7 +87,7 @@ public void test1() throws Exception { @Test public void test2() throws Exception { CircuitBreaker> cb = new CircuitBreaker<>(invocation(), "test invocation", - new ExceptionDecision(testException, SetOfThrowables.EMPTY, false), + new SetBasedExceptionDecision(testException, SetOfThrowables.EMPTY, false), 1000, 4, 0.5, 2, stopwatch); // circuit breaker is closed diff --git a/implementation/core/src/test/java/io/smallrye/faulttolerance/core/retry/CompletionStageRetryTest.java b/implementation/core/src/test/java/io/smallrye/faulttolerance/core/retry/CompletionStageRetryTest.java index 127598b4..18fee120 100644 --- a/implementation/core/src/test/java/io/smallrye/faulttolerance/core/retry/CompletionStageRetryTest.java +++ b/implementation/core/src/test/java/io/smallrye/faulttolerance/core/retry/CompletionStageRetryTest.java @@ -20,6 +20,7 @@ import io.smallrye.faulttolerance.core.async.CompletionStageExecution; import io.smallrye.faulttolerance.core.stopwatch.TestStopwatch; import io.smallrye.faulttolerance.core.util.ExceptionDecision; +import io.smallrye.faulttolerance.core.util.SetBasedExceptionDecision; import io.smallrye.faulttolerance.core.util.SetOfThrowables; public class CompletionStageRetryTest { @@ -67,7 +68,7 @@ public void shouldPropagateAbortOnError() { }); CompletionStageExecution execution = new CompletionStageExecution<>(invocation, executor); CompletionStageRetry retry = new CompletionStageRetry<>(execution, "shouldPropagateAbortOnError", - new ExceptionDecision(SetOfThrowables.ALL, SetOfThrowables.create(RuntimeException.class), false), + new SetBasedExceptionDecision(SetOfThrowables.ALL, SetOfThrowables.create(RuntimeException.class), false), 3, 1000L, AsyncDelay.NONE, new TestStopwatch()); CompletionStage result = retry.apply(new InvocationContext<>(() -> completedFuture("ignored"))); @@ -88,7 +89,7 @@ public void shouldPropagateAbortOnErrorInCSCreation() { }); CompletionStageExecution execution = new CompletionStageExecution<>(invocation, executor); CompletionStageRetry retry = new CompletionStageRetry<>(execution, "shouldPropagateAbortOnErrorInCSCreation", - new ExceptionDecision(SetOfThrowables.ALL, SetOfThrowables.create(RuntimeException.class), false), + new SetBasedExceptionDecision(SetOfThrowables.ALL, SetOfThrowables.create(RuntimeException.class), false), 3, 1000L, AsyncDelay.NONE, new TestStopwatch()); CompletionStage result = retry.apply(new InvocationContext<>(() -> completedFuture("ignored"))); @@ -113,7 +114,7 @@ public void shouldRetryOnce() throws Exception { }); CompletionStageExecution execution = new CompletionStageExecution<>(invocation, executor); CompletionStageRetry retry = new CompletionStageRetry<>(execution, "shouldRetryOnce", - new ExceptionDecision(SetOfThrowables.create(RuntimeException.class), SetOfThrowables.EMPTY, false), + new SetBasedExceptionDecision(SetOfThrowables.create(RuntimeException.class), SetOfThrowables.EMPTY, false), 3, 1000L, AsyncDelay.NONE, new TestStopwatch()); CompletionStage result = retry.apply(new InvocationContext<>(() -> completedFuture("ignored"))); @@ -139,7 +140,7 @@ public void shouldRetryOnceOnCsFailure() throws Exception { }); CompletionStageExecution execution = new CompletionStageExecution<>(invocation, executor); CompletionStageRetry retry = new CompletionStageRetry<>(execution, "shouldRetryOnceOnCsFailure", - new ExceptionDecision(SetOfThrowables.create(RuntimeException.class), SetOfThrowables.EMPTY, false), + new SetBasedExceptionDecision(SetOfThrowables.create(RuntimeException.class), SetOfThrowables.EMPTY, false), 3, 1000L, AsyncDelay.NONE, new TestStopwatch()); CompletionStage result = retry.apply(new InvocationContext<>(() -> completedFuture("ignored"))); @@ -165,7 +166,7 @@ public void shouldRetryMaxTimesAndSucceed() throws Exception { }); CompletionStageExecution execution = new CompletionStageExecution<>(invocation, executor); CompletionStageRetry retry = new CompletionStageRetry<>(execution, "shouldRetryMaxTimesAndSucceed", - new ExceptionDecision(SetOfThrowables.create(RuntimeException.class), SetOfThrowables.EMPTY, false), + new SetBasedExceptionDecision(SetOfThrowables.create(RuntimeException.class), SetOfThrowables.EMPTY, false), 3, 1000L, AsyncDelay.NONE, new TestStopwatch()); CompletionStage result = retry.apply(new InvocationContext<>(() -> completedFuture("ignored"))); @@ -185,7 +186,7 @@ public void shouldRetryMaxTimesAndFail() { })); CompletionStageExecution execution = new CompletionStageExecution<>(invocation, executor); CompletionStageRetry retry = new CompletionStageRetry<>(execution, "shouldRetryMaxTimesAndSucceed", - new ExceptionDecision(SetOfThrowables.create(RuntimeException.class), SetOfThrowables.EMPTY, false), + new SetBasedExceptionDecision(SetOfThrowables.create(RuntimeException.class), SetOfThrowables.EMPTY, false), 3, 1000L, AsyncDelay.NONE, new TestStopwatch()); CompletionStage result = retry.apply(new InvocationContext<>(() -> completedFuture("ignored"))); diff --git a/implementation/core/src/test/java/io/smallrye/faulttolerance/core/retry/FutureRetryTest.java b/implementation/core/src/test/java/io/smallrye/faulttolerance/core/retry/FutureRetryTest.java index e31b6500..5a5dbc4f 100644 --- a/implementation/core/src/test/java/io/smallrye/faulttolerance/core/retry/FutureRetryTest.java +++ b/implementation/core/src/test/java/io/smallrye/faulttolerance/core/retry/FutureRetryTest.java @@ -13,6 +13,7 @@ import io.smallrye.faulttolerance.core.stopwatch.TestStopwatch; import io.smallrye.faulttolerance.core.util.ExceptionDecision; +import io.smallrye.faulttolerance.core.util.SetBasedExceptionDecision; import io.smallrye.faulttolerance.core.util.SetOfThrowables; import io.smallrye.faulttolerance.core.util.TestException; import io.smallrye.faulttolerance.core.util.TestThread; @@ -37,7 +38,7 @@ public void immediatelyReturning_failedFuture() throws Exception { RuntimeException exception = new RuntimeException(); TestInvocation> invocation = TestInvocation.immediatelyReturning(() -> failedFuture(exception)); Retry> futureRetry = new Retry<>(invocation, "test invocation", - ExceptionDecision.EMPTY, 3, 1000, SyncDelay.NONE, stopwatch); + ExceptionDecision.ALWAYS_EXPECTED, 3, 1000, SyncDelay.NONE, stopwatch); Future result = runOnTestThread(futureRetry).await(); assertThatThrownBy(result::get).hasCause(exception); assertThat(invocation.numberOfInvocations()).isEqualTo(1); @@ -47,7 +48,7 @@ public void immediatelyReturning_failedFuture() throws Exception { public void immediatelyReturning_value() throws Exception { TestInvocation> invocation = TestInvocation.immediatelyReturning(() -> completedFuture("foobar")); Future result = runOnTestThread( - new Retry<>(invocation, "test invocation", ExceptionDecision.EMPTY, 3, 1000, + new Retry<>(invocation, "test invocation", ExceptionDecision.ALWAYS_EXPECTED, 3, 1000, SyncDelay.NONE, stopwatch)).await(); assertThat(result.get()).isEqualTo("foobar"); assertThat(invocation.numberOfInvocations()).isEqualTo(1); @@ -63,7 +64,7 @@ public void immediatelyReturning_interruptedInInvocation() throws InterruptedExc return completedFuture("foobar"); }); TestThread> executingThread = runOnTestThread(new Retry<>(invocation, "test invocation", - ExceptionDecision.EMPTY, 3, 1000, SyncDelay.NONE, stopwatch)); + ExceptionDecision.ALWAYS_EXPECTED, 3, 1000, SyncDelay.NONE, stopwatch)); startInvocationBarrier.await(); executingThread.interrupt(); assertThatThrownBy(executingThread::await).isInstanceOf(InterruptedException.class); @@ -81,7 +82,7 @@ public void immediatelyReturning_selfInterruptedInInvocation() throws Interrupte throw new RuntimeException(); }); TestThread> executingThread = runOnTestThread(new Retry<>(invocation, "test invocation", - ExceptionDecision.EMPTY, 3, 1000, SyncDelay.NONE, stopwatch)); + ExceptionDecision.ALWAYS_EXPECTED, 3, 1000, SyncDelay.NONE, stopwatch)); startInvocationBarrier.await(); endInvocationBarrier.open(); assertThatThrownBy(executingThread::await).isInstanceOf(InterruptedException.class); @@ -93,7 +94,7 @@ public void initiallyFailing_retriedExceptionThenValue_equalToMaxRetries() throw TestInvocation> invocation = TestInvocation.initiallyFailing(3, RuntimeException::new, () -> completedFuture("foobar")); TestThread> result = runOnTestThread(new Retry<>(invocation, "test invocation", - new ExceptionDecision(exception, SetOfThrowables.EMPTY, false), + new SetBasedExceptionDecision(exception, SetOfThrowables.EMPTY, false), 3, 1000, SyncDelay.NONE, stopwatch)); assertThat(result.await().get()).isEqualTo("foobar"); assertThat(invocation.numberOfInvocations()).isEqualTo(4); @@ -104,7 +105,7 @@ public void initiallyFailing_retriedExceptionThenValue_moreThanMaxRetries() { TestInvocation> invocation = TestInvocation.initiallyFailing(4, RuntimeException::new, () -> completedFuture("foobar")); TestThread> result = runOnTestThread(new Retry<>(invocation, "test invocation", - new ExceptionDecision(exception, SetOfThrowables.EMPTY, false), + new SetBasedExceptionDecision(exception, SetOfThrowables.EMPTY, false), 3, 1000, SyncDelay.NONE, stopwatch)); assertThatThrownBy(result::await).isExactlyInstanceOf(RuntimeException.class); assertThat(invocation.numberOfInvocations()).isEqualTo(4); @@ -115,7 +116,7 @@ public void initiallyFailing_retriedExceptionThenRetriedException_equalToMaxRetr TestInvocation> invocation = TestInvocation.initiallyFailing(3, RuntimeException::new, TestException::doThrow); TestThread> result = runOnTestThread(new Retry<>(invocation, "test invocation", - new ExceptionDecision(exception, SetOfThrowables.EMPTY, false), + new SetBasedExceptionDecision(exception, SetOfThrowables.EMPTY, false), 3, 1000, SyncDelay.NONE, stopwatch)); assertThatThrownBy(result::await).isExactlyInstanceOf(TestException.class); assertThat(invocation.numberOfInvocations()).isEqualTo(4); @@ -131,7 +132,7 @@ public void initiallyFailing_retriedExceptionThenValue_interruptedInInvocation() return completedFuture("foobar"); }); TestThread> executingThread = runOnTestThread(new Retry<>(invocation, "test invocation", - new ExceptionDecision(exception, testException, false), + new SetBasedExceptionDecision(exception, testException, false), 3, 1000, SyncDelay.NONE, stopwatch)); startInvocationBarrier.await(); executingThread.interrupt(); diff --git a/implementation/core/src/test/java/io/smallrye/faulttolerance/core/retry/RetryTest.java b/implementation/core/src/test/java/io/smallrye/faulttolerance/core/retry/RetryTest.java index e0b50302..8a1342cc 100644 --- a/implementation/core/src/test/java/io/smallrye/faulttolerance/core/retry/RetryTest.java +++ b/implementation/core/src/test/java/io/smallrye/faulttolerance/core/retry/RetryTest.java @@ -9,6 +9,7 @@ import io.smallrye.faulttolerance.core.stopwatch.TestStopwatch; import io.smallrye.faulttolerance.core.util.ExceptionDecision; +import io.smallrye.faulttolerance.core.util.SetBasedExceptionDecision; import io.smallrye.faulttolerance.core.util.SetOfThrowables; import io.smallrye.faulttolerance.core.util.TestException; import io.smallrye.faulttolerance.core.util.TestThread; @@ -29,7 +30,7 @@ public void setUp() { public void immediatelyReturning_value() throws Exception { TestInvocation invocation = TestInvocation.immediatelyReturning(() -> "foobar"); TestThread result = runOnTestThread(new Retry<>(invocation, "test invocation", - ExceptionDecision.EMPTY, 3, 1000, SyncDelay.NONE, stopwatch)); + ExceptionDecision.ALWAYS_EXPECTED, 3, 1000, SyncDelay.NONE, stopwatch)); assertThat(result.await()).isEqualTo("foobar"); assertThat(invocation.numberOfInvocations()).isEqualTo(1); } @@ -38,7 +39,7 @@ public void immediatelyReturning_value() throws Exception { public void immediatelyReturning_retriedException() { TestInvocation invocation = TestInvocation.immediatelyReturning(TestException::doThrow); TestThread result = runOnTestThread(new Retry<>(invocation, "test invocation", - new ExceptionDecision(exception, SetOfThrowables.EMPTY, false), + new SetBasedExceptionDecision(exception, SetOfThrowables.EMPTY, false), 3, 1000, SyncDelay.NONE, stopwatch)); assertThatThrownBy(result::await).isExactlyInstanceOf(TestException.class); assertThat(invocation.numberOfInvocations()).isEqualTo(4); @@ -48,7 +49,7 @@ public void immediatelyReturning_retriedException() { public void immediatelyReturning_abortingException() { TestInvocation invocation = TestInvocation.immediatelyReturning(TestException::doThrow); TestThread result = runOnTestThread(new Retry<>(invocation, "test invocation", - new ExceptionDecision(exception, testException, false), + new SetBasedExceptionDecision(exception, testException, false), 3, 1000, SyncDelay.NONE, stopwatch)); assertThatThrownBy(result::await).isExactlyInstanceOf(TestException.class); assertThat(invocation.numberOfInvocations()).isEqualTo(1); @@ -58,7 +59,7 @@ public void immediatelyReturning_abortingException() { public void immediatelyReturning_unknownException() { TestInvocation invocation = TestInvocation.immediatelyReturning(TestException::doThrow); TestThread result = runOnTestThread(new Retry<>(invocation, "test invocation", - ExceptionDecision.EMPTY, 3, 1000, SyncDelay.NONE, stopwatch)); + ExceptionDecision.ALWAYS_EXPECTED, 3, 1000, SyncDelay.NONE, stopwatch)); assertThatThrownBy(result::await).isExactlyInstanceOf(TestException.class); assertThat(invocation.numberOfInvocations()).isEqualTo(1); } @@ -73,7 +74,7 @@ public void immediatelyReturning_interruptedInInvocation() throws InterruptedExc return "foobar"; }); TestThread executingThread = runOnTestThread(new Retry<>(invocation, "test invocation", - ExceptionDecision.EMPTY, 3, 1000, SyncDelay.NONE, stopwatch)); + ExceptionDecision.ALWAYS_EXPECTED, 3, 1000, SyncDelay.NONE, stopwatch)); startInvocationBarrier.await(); executingThread.interrupt(); assertThatThrownBy(executingThread::await).isInstanceOf(InterruptedException.class); @@ -91,7 +92,7 @@ public void immediatelyReturning_selfInterruptedInInvocation() throws Interrupte throw new RuntimeException(); }); TestThread executingThread = runOnTestThread(new Retry<>(invocation, "test invocation", - ExceptionDecision.EMPTY, 3, 1000, SyncDelay.NONE, stopwatch)); + ExceptionDecision.ALWAYS_EXPECTED, 3, 1000, SyncDelay.NONE, stopwatch)); startInvocationBarrier.await(); endInvocationBarrier.open(); assertThatThrownBy(executingThread::await).isInstanceOf(InterruptedException.class); @@ -102,7 +103,7 @@ public void immediatelyReturning_selfInterruptedInInvocation() throws Interrupte public void initiallyFailing_retriedExceptionThenValue_lessThanMaxRetries() throws Exception { TestInvocation invocation = TestInvocation.initiallyFailing(2, RuntimeException::new, () -> "foobar"); TestThread result = runOnTestThread(new Retry<>(invocation, "test invocation", - new ExceptionDecision(exception, SetOfThrowables.EMPTY, false), + new SetBasedExceptionDecision(exception, SetOfThrowables.EMPTY, false), 3, 1000, SyncDelay.NONE, stopwatch)); assertThat(result.await()).isEqualTo("foobar"); assertThat(invocation.numberOfInvocations()).isEqualTo(3); @@ -112,7 +113,7 @@ public void initiallyFailing_retriedExceptionThenValue_lessThanMaxRetries() thro public void initiallyFailing_retriedExceptionThenValue_equalToMaxRetries() throws Exception { TestInvocation invocation = TestInvocation.initiallyFailing(3, RuntimeException::new, () -> "foobar"); TestThread result = runOnTestThread(new Retry<>(invocation, "test invocation", - new ExceptionDecision(exception, SetOfThrowables.EMPTY, false), + new SetBasedExceptionDecision(exception, SetOfThrowables.EMPTY, false), 3, 1000, SyncDelay.NONE, stopwatch)); assertThat(result.await()).isEqualTo("foobar"); assertThat(invocation.numberOfInvocations()).isEqualTo(4); @@ -122,7 +123,7 @@ public void initiallyFailing_retriedExceptionThenValue_equalToMaxRetries() throw public void initiallyFailing_retriedExceptionThenValue_moreThanMaxRetries() { TestInvocation invocation = TestInvocation.initiallyFailing(4, RuntimeException::new, () -> "foobar"); TestThread result = runOnTestThread(new Retry<>(invocation, "test invocation", - new ExceptionDecision(exception, SetOfThrowables.EMPTY, false), + new SetBasedExceptionDecision(exception, SetOfThrowables.EMPTY, false), 3, 1000, SyncDelay.NONE, stopwatch)); assertThatThrownBy(result::await).isExactlyInstanceOf(RuntimeException.class); assertThat(invocation.numberOfInvocations()).isEqualTo(4); @@ -132,7 +133,7 @@ public void initiallyFailing_retriedExceptionThenValue_moreThanMaxRetries() { public void initiallyFailing_retriedExceptionThenRetriedException_lessThanMaxRetries() { TestInvocation invocation = TestInvocation.initiallyFailing(2, RuntimeException::new, TestException::doThrow); TestThread result = runOnTestThread(new Retry<>(invocation, "test invocation", - new ExceptionDecision(exception, SetOfThrowables.EMPTY, false), + new SetBasedExceptionDecision(exception, SetOfThrowables.EMPTY, false), 3, 1000, SyncDelay.NONE, stopwatch)); assertThatThrownBy(result::await).isExactlyInstanceOf(TestException.class); assertThat(invocation.numberOfInvocations()).isEqualTo(4); @@ -142,7 +143,7 @@ public void initiallyFailing_retriedExceptionThenRetriedException_lessThanMaxRet public void initiallyFailing_retriedExceptionThenRetriedException_equalToMaxRetries() { TestInvocation invocation = TestInvocation.initiallyFailing(3, RuntimeException::new, TestException::doThrow); TestThread result = runOnTestThread(new Retry<>(invocation, "test invocation", - new ExceptionDecision(exception, SetOfThrowables.EMPTY, false), + new SetBasedExceptionDecision(exception, SetOfThrowables.EMPTY, false), 3, 1000, SyncDelay.NONE, stopwatch)); assertThatThrownBy(result::await).isExactlyInstanceOf(TestException.class); assertThat(invocation.numberOfInvocations()).isEqualTo(4); @@ -152,7 +153,7 @@ public void initiallyFailing_retriedExceptionThenRetriedException_equalToMaxRetr public void initiallyFailing_retriedExceptionThenRetriedException_moreThanMaxRetries() { TestInvocation invocation = TestInvocation.initiallyFailing(4, RuntimeException::new, TestException::doThrow); TestThread result = runOnTestThread(new Retry<>(invocation, "test invocation", - new ExceptionDecision(exception, SetOfThrowables.EMPTY, false), + new SetBasedExceptionDecision(exception, SetOfThrowables.EMPTY, false), 3, 1000, SyncDelay.NONE, stopwatch)); assertThatThrownBy(result::await).isExactlyInstanceOf(RuntimeException.class); assertThat(invocation.numberOfInvocations()).isEqualTo(4); @@ -162,7 +163,7 @@ public void initiallyFailing_retriedExceptionThenRetriedException_moreThanMaxRet public void initiallyFailing_retriedExceptionThenAbortingException_lessThanMaxRetries() { TestInvocation invocation = TestInvocation.initiallyFailing(2, RuntimeException::new, TestException::doThrow); TestThread result = runOnTestThread(new Retry<>(invocation, "test invocation", - new ExceptionDecision(exception, testException, false), + new SetBasedExceptionDecision(exception, testException, false), 3, 1000, SyncDelay.NONE, stopwatch)); assertThatThrownBy(result::await).isExactlyInstanceOf(TestException.class); assertThat(invocation.numberOfInvocations()).isEqualTo(3); @@ -172,7 +173,7 @@ public void initiallyFailing_retriedExceptionThenAbortingException_lessThanMaxRe public void initiallyFailing_retriedExceptionThenAbortingException_equalToMaxRetries() { TestInvocation invocation = TestInvocation.initiallyFailing(3, RuntimeException::new, TestException::doThrow); TestThread result = runOnTestThread(new Retry<>(invocation, "test invocation", - new ExceptionDecision(exception, testException, false), + new SetBasedExceptionDecision(exception, testException, false), 3, 1000, SyncDelay.NONE, stopwatch)); assertThatThrownBy(result::await).isExactlyInstanceOf(TestException.class); assertThat(invocation.numberOfInvocations()).isEqualTo(4); @@ -182,7 +183,7 @@ public void initiallyFailing_retriedExceptionThenAbortingException_equalToMaxRet public void initiallyFailing_retriedExceptionThenAbortingException_moreThanMaxRetries() { TestInvocation invocation = TestInvocation.initiallyFailing(4, RuntimeException::new, TestException::doThrow); TestThread result = runOnTestThread(new Retry<>(invocation, "test invocation", - new ExceptionDecision(exception, testException, false), + new SetBasedExceptionDecision(exception, testException, false), 3, 1000, SyncDelay.NONE, stopwatch)); assertThatThrownBy(result::await).isExactlyInstanceOf(RuntimeException.class); assertThat(invocation.numberOfInvocations()).isEqualTo(4); @@ -195,7 +196,7 @@ public void initiallyFailing_retriedExceptionThenValue_totalDelayLessThanMaxDura TestDelay delay = TestDelay.normal(startDelayBarrier, endDelayBarrier); TestInvocation invocation = TestInvocation.initiallyFailing(3, RuntimeException::new, () -> "foobar"); TestThread result = runOnTestThread(new Retry<>(invocation, "test invocation", - new ExceptionDecision(exception, SetOfThrowables.EMPTY, false), + new SetBasedExceptionDecision(exception, SetOfThrowables.EMPTY, false), 3, 1000, () -> delay, stopwatch)); startDelayBarrier.await(); stopwatch.setCurrentValue(500); @@ -211,7 +212,7 @@ public void initiallyFailing_retriedExceptionThenValue_totalDelayEqualToMaxDurat TestDelay delay = TestDelay.normal(startDelayBarrier, endDelayBarrier); TestInvocation invocation = TestInvocation.initiallyFailing(3, RuntimeException::new, () -> "foobar"); TestThread result = runOnTestThread(new Retry<>(invocation, "test invocation", - new ExceptionDecision(exception, SetOfThrowables.EMPTY, false), + new SetBasedExceptionDecision(exception, SetOfThrowables.EMPTY, false), 3, 1000, () -> delay, stopwatch)); startDelayBarrier.await(); stopwatch.setCurrentValue(1000); @@ -227,7 +228,7 @@ public void initiallyFailing_retriedExceptionThenValue_totalDelayMoreThanMaxDura TestDelay delay = TestDelay.normal(startDelayBarrier, endDelayBarrier); TestInvocation invocation = TestInvocation.initiallyFailing(3, RuntimeException::new, () -> "foobar"); TestThread result = runOnTestThread(new Retry<>(invocation, "test invocation", - new ExceptionDecision(exception, SetOfThrowables.EMPTY, false), + new SetBasedExceptionDecision(exception, SetOfThrowables.EMPTY, false), 3, 1000, () -> delay, stopwatch)); startDelayBarrier.await(); stopwatch.setCurrentValue(1500); @@ -244,7 +245,7 @@ public void initiallyFailing_retriedExceptionThenRetriedException_totalDelayLess TestDelay delay = TestDelay.normal(startDelayBarrier, endDelayBarrier); TestInvocation invocation = TestInvocation.initiallyFailing(3, RuntimeException::new, TestException::doThrow); TestThread result = runOnTestThread(new Retry<>(invocation, "test invocation", - new ExceptionDecision(exception, SetOfThrowables.EMPTY, false), + new SetBasedExceptionDecision(exception, SetOfThrowables.EMPTY, false), 3, 1000, () -> delay, stopwatch)); startDelayBarrier.await(); stopwatch.setCurrentValue(500); @@ -261,7 +262,7 @@ public void initiallyFailing_retriedExceptionThenRetriedException_totalDelayEqua TestDelay delay = TestDelay.normal(startDelayBarrier, endDelayBarrier); TestInvocation invocation = TestInvocation.initiallyFailing(3, RuntimeException::new, TestException::doThrow); TestThread result = runOnTestThread(new Retry<>(invocation, "test invocation", - new ExceptionDecision(exception, SetOfThrowables.EMPTY, false), + new SetBasedExceptionDecision(exception, SetOfThrowables.EMPTY, false), 3, 1000, () -> delay, stopwatch)); startDelayBarrier.await(); stopwatch.setCurrentValue(1000); @@ -278,7 +279,7 @@ public void initiallyFailing_retriedExceptionThenRetriedException_totalDelayMore TestDelay delay = TestDelay.normal(startDelayBarrier, endDelayBarrier); TestInvocation invocation = TestInvocation.initiallyFailing(3, RuntimeException::new, TestException::doThrow); TestThread result = runOnTestThread(new Retry<>(invocation, "test invocation", - new ExceptionDecision(exception, SetOfThrowables.EMPTY, false), + new SetBasedExceptionDecision(exception, SetOfThrowables.EMPTY, false), 3, 1000, () -> delay, stopwatch)); startDelayBarrier.await(); stopwatch.setCurrentValue(1500); @@ -295,7 +296,7 @@ public void initiallyFailing_retriedExceptionThenAbortingException_totalDelayLes TestDelay delay = TestDelay.normal(startDelayBarrier, endDelayBarrier); TestInvocation invocation = TestInvocation.initiallyFailing(3, RuntimeException::new, TestException::doThrow); TestThread result = runOnTestThread(new Retry<>(invocation, "test invocation", - new ExceptionDecision(exception, testException, false), + new SetBasedExceptionDecision(exception, testException, false), 3, 1000, () -> delay, stopwatch)); startDelayBarrier.await(); stopwatch.setCurrentValue(500); @@ -312,7 +313,7 @@ public void initiallyFailing_retriedExceptionThenAbortingException_totalDelayEqu TestDelay delay = TestDelay.normal(startDelayBarrier, endDelayBarrier); TestInvocation invocation = TestInvocation.initiallyFailing(3, RuntimeException::new, TestException::doThrow); TestThread result = runOnTestThread(new Retry<>(invocation, "test invocation", - new ExceptionDecision(exception, testException, false), + new SetBasedExceptionDecision(exception, testException, false), 3, 1000, () -> delay, stopwatch)); startDelayBarrier.await(); stopwatch.setCurrentValue(1000); @@ -329,7 +330,7 @@ public void initiallyFailing_retriedExceptionThenAbortingException_totalDelayMor TestDelay delay = TestDelay.normal(startDelayBarrier, endDelayBarrier); TestInvocation invocation = TestInvocation.initiallyFailing(3, RuntimeException::new, TestException::doThrow); TestThread result = runOnTestThread(new Retry<>(invocation, "test invocation", - new ExceptionDecision(exception, testException, false), + new SetBasedExceptionDecision(exception, testException, false), 3, 1000, () -> delay, stopwatch)); startDelayBarrier.await(); stopwatch.setCurrentValue(1500); @@ -342,7 +343,7 @@ public void initiallyFailing_retriedExceptionThenAbortingException_totalDelayMor public void initiallyFailing_retriedExceptionThenValue_infiniteRetries() throws Exception { TestInvocation invocation = TestInvocation.initiallyFailing(10, RuntimeException::new, () -> "foobar"); TestThread result = runOnTestThread(new Retry<>(invocation, "test invocation", - new ExceptionDecision(exception, SetOfThrowables.EMPTY, false), + new SetBasedExceptionDecision(exception, SetOfThrowables.EMPTY, false), -1, 1000, SyncDelay.NONE, stopwatch)); assertThat(result.await()).isEqualTo("foobar"); assertThat(invocation.numberOfInvocations()).isEqualTo(11); @@ -352,7 +353,7 @@ public void initiallyFailing_retriedExceptionThenValue_infiniteRetries() throws public void initiallyFailing_retriedExceptionThenAbortingException_infiniteRetries() { TestInvocation invocation = TestInvocation.initiallyFailing(10, RuntimeException::new, TestException::doThrow); TestThread result = runOnTestThread(new Retry<>(invocation, "test invocation", - new ExceptionDecision(exception, testException, false), + new SetBasedExceptionDecision(exception, testException, false), -1, 1000, SyncDelay.NONE, stopwatch)); assertThatThrownBy(result::await).isExactlyInstanceOf(TestException.class); assertThat(invocation.numberOfInvocations()).isEqualTo(11); @@ -365,7 +366,7 @@ public void initiallyFailing_retriedExceptionThenValue_infiniteDuration() throws TestDelay delay = TestDelay.normal(startDelayBarrier, endDelayBarrier); TestInvocation invocation = TestInvocation.initiallyFailing(3, RuntimeException::new, () -> "foobar"); TestThread result = runOnTestThread(new Retry<>(invocation, "test invocation", - new ExceptionDecision(exception, SetOfThrowables.EMPTY, false), + new SetBasedExceptionDecision(exception, SetOfThrowables.EMPTY, false), 3, -1, () -> delay, stopwatch)); startDelayBarrier.await(); stopwatch.setCurrentValue(1_000_000_000L); @@ -381,7 +382,7 @@ public void initiallyFailing_retriedExceptionThenRetriedException_infiniteDurati TestDelay delay = TestDelay.normal(startDelayBarrier, endDelayBarrier); TestInvocation invocation = TestInvocation.initiallyFailing(3, RuntimeException::new, TestException::doThrow); TestThread result = runOnTestThread(new Retry<>(invocation, "test invocation", - new ExceptionDecision(exception, SetOfThrowables.EMPTY, false), + new SetBasedExceptionDecision(exception, SetOfThrowables.EMPTY, false), 3, -1, () -> delay, stopwatch)); startDelayBarrier.await(); stopwatch.setCurrentValue(1_000_000_000L); @@ -397,7 +398,7 @@ public void initiallyFailing_retriedExceptionThenAbortingException_infiniteDurat TestDelay delay = TestDelay.normal(startDelayBarrier, endDelayBarrier); TestInvocation invocation = TestInvocation.initiallyFailing(3, RuntimeException::new, TestException::doThrow); TestThread result = runOnTestThread(new Retry<>(invocation, "test invocation", - new ExceptionDecision(exception, testException, false), + new SetBasedExceptionDecision(exception, testException, false), 3, -1, () -> delay, stopwatch)); startDelayBarrier.await(); stopwatch.setCurrentValue(1_000_000_000L); @@ -416,7 +417,7 @@ public void initiallyFailing_retriedExceptionThenValue_interruptedInInvocation() return "foobar"; }); TestThread executingThread = runOnTestThread(new Retry<>(invocation, "test invocation", - new ExceptionDecision(exception, testException, false), + new SetBasedExceptionDecision(exception, testException, false), 3, 1000, SyncDelay.NONE, stopwatch)); startInvocationBarrier.await(); executingThread.interrupt(); @@ -434,7 +435,7 @@ public void initiallyFailing_retriedExceptionThenRetriedException_interruptedInI throw new TestException(); }); TestThread executingThread = runOnTestThread(new Retry<>(invocation, "test invocation", - new ExceptionDecision(exception, SetOfThrowables.EMPTY, false), + new SetBasedExceptionDecision(exception, SetOfThrowables.EMPTY, false), 3, 1000, SyncDelay.NONE, stopwatch)); startInvocationBarrier.await(); executingThread.interrupt(); @@ -452,7 +453,7 @@ public void initiallyFailing_retriedExceptionThenAbortingException_interruptedIn throw new TestException(); }); TestThread executingThread = runOnTestThread(new Retry<>(invocation, "test invocation", - new ExceptionDecision(exception, testException, false), + new SetBasedExceptionDecision(exception, testException, false), 3, 1000, SyncDelay.NONE, stopwatch)); startInvocationBarrier.await(); executingThread.interrupt(); @@ -467,7 +468,7 @@ public void initiallyFailing_retriedExceptionThenValue_interruptedInDelay() thro TestDelay delay = TestDelay.normal(startDelayBarrier, endDelayBarrier); TestInvocation invocation = TestInvocation.initiallyFailing(3, RuntimeException::new, () -> "foobar"); TestThread executingThread = runOnTestThread(new Retry<>(invocation, "test invocation", - new ExceptionDecision(exception, testException, false), + new SetBasedExceptionDecision(exception, testException, false), 3, 1000, () -> delay, stopwatch)); startDelayBarrier.await(); executingThread.interrupt(); @@ -482,7 +483,7 @@ public void initiallyFailing_retriedExceptionThenRetriedException_interruptedInD TestDelay delay = TestDelay.normal(startDelayBarrier, endDelayBarrier); TestInvocation invocation = TestInvocation.initiallyFailing(3, RuntimeException::new, TestException::doThrow); TestThread executingThread = runOnTestThread(new Retry<>(invocation, "test invocation", - new ExceptionDecision(exception, SetOfThrowables.EMPTY, false), + new SetBasedExceptionDecision(exception, SetOfThrowables.EMPTY, false), 3, 1000, () -> delay, stopwatch)); startDelayBarrier.await(); executingThread.interrupt(); @@ -497,7 +498,7 @@ public void initiallyFailing_retriedExceptionThenAbortingException_interruptedIn TestDelay delay = TestDelay.normal(startDelayBarrier, endDelayBarrier); TestInvocation invocation = TestInvocation.initiallyFailing(3, RuntimeException::new, TestException::doThrow); TestThread executingThread = runOnTestThread(new Retry<>(invocation, "test invocation", - new ExceptionDecision(exception, testException, false), + new SetBasedExceptionDecision(exception, testException, false), 3, 1000, () -> delay, stopwatch)); startDelayBarrier.await(); executingThread.interrupt(); @@ -512,7 +513,7 @@ public void initiallyFailing_retriedExceptionThenValue_unexpectedExceptionInDela TestDelay delay = TestDelay.exceptionThrowing(startDelayBarrier, endDelayBarrier, RuntimeException::new); TestInvocation invocation = TestInvocation.initiallyFailing(3, RuntimeException::new, () -> "foobar"); TestThread executingThread = runOnTestThread(new Retry<>(invocation, "test invocation", - new ExceptionDecision(exception, testException, false), + new SetBasedExceptionDecision(exception, testException, false), 3, 1000, () -> delay, stopwatch)); startDelayBarrier.await(); endDelayBarrier.open(); @@ -527,7 +528,7 @@ public void initiallyFailing_retriedExceptionThenRetriedException_unexpectedExce TestDelay delay = TestDelay.exceptionThrowing(startDelayBarrier, endDelayBarrier, RuntimeException::new); TestInvocation invocation = TestInvocation.initiallyFailing(3, RuntimeException::new, TestException::doThrow); TestThread executingThread = runOnTestThread(new Retry<>(invocation, "test invocation", - new ExceptionDecision(exception, SetOfThrowables.EMPTY, false), + new SetBasedExceptionDecision(exception, SetOfThrowables.EMPTY, false), 3, 1000, () -> delay, stopwatch)); startDelayBarrier.await(); endDelayBarrier.open(); @@ -543,7 +544,7 @@ public void initiallyFailing_retriedExceptionThenAbortingException_unexpectedExc TestDelay delay = TestDelay.exceptionThrowing(startDelayBarrier, endDelayBarrier, RuntimeException::new); TestInvocation invocation = TestInvocation.initiallyFailing(3, RuntimeException::new, TestException::doThrow); TestThread executingThread = runOnTestThread(new Retry<>(invocation, "test invocation", - new ExceptionDecision(exception, testException, false), + new SetBasedExceptionDecision(exception, testException, false), 3, 1000, () -> delay, stopwatch)); startDelayBarrier.await(); endDelayBarrier.open(); @@ -562,7 +563,7 @@ public void initiallyFailing_retriedExceptionThenSelfInterrupt() throws Interrup throw new RuntimeException(); }); TestThread executingThread = runOnTestThread(new Retry<>(invocation, "test invocation", - new ExceptionDecision(exception, SetOfThrowables.EMPTY, false), + new SetBasedExceptionDecision(exception, SetOfThrowables.EMPTY, false), 3, 1000, SyncDelay.NONE, stopwatch)); startInvocationBarrier.await(); endInvocationBarrier.open(); @@ -577,7 +578,7 @@ public void initiallyFailing_retriedExceptionThenValue_selfInterruptedInDelay() TestDelay delay = TestDelay.selfInterrupting(startDelayBarrier, endDelayBarrier); TestInvocation invocation = TestInvocation.initiallyFailing(3, RuntimeException::new, () -> "foobar"); TestThread executingThread = runOnTestThread(new Retry<>(invocation, "test invocation", - new ExceptionDecision(exception, testException, false), + new SetBasedExceptionDecision(exception, testException, false), 3, 1000, () -> delay, stopwatch)); startDelayBarrier.await(); endDelayBarrier.open(); @@ -592,7 +593,7 @@ public void initiallyFailing_retriedExceptionThenRetriedException_selfInterrupte TestDelay delay = TestDelay.selfInterrupting(startDelayBarrier, endDelayBarrier); TestInvocation invocation = TestInvocation.initiallyFailing(3, RuntimeException::new, TestException::doThrow); TestThread executingThread = runOnTestThread(new Retry<>(invocation, "test invocation", - new ExceptionDecision(exception, SetOfThrowables.EMPTY, false), + new SetBasedExceptionDecision(exception, SetOfThrowables.EMPTY, false), 3, 1000, () -> delay, stopwatch)); startDelayBarrier.await(); endDelayBarrier.open(); @@ -607,7 +608,7 @@ public void initiallyFailing_retriedExceptionThenAbortingException_selfInterrupt TestDelay delay = TestDelay.selfInterrupting(startDelayBarrier, endDelayBarrier); TestInvocation invocation = TestInvocation.initiallyFailing(3, RuntimeException::new, TestException::doThrow); TestThread executingThread = runOnTestThread(new Retry<>(invocation, "test invocation", - new ExceptionDecision(exception, testException, false), + new SetBasedExceptionDecision(exception, testException, false), 3, 1000, () -> delay, stopwatch)); startDelayBarrier.await(); endDelayBarrier.open(); diff --git a/implementation/core/src/test/java/io/smallrye/faulttolerance/core/util/ExceptionDecisionTest.java b/implementation/core/src/test/java/io/smallrye/faulttolerance/core/util/ExceptionDecisionTest.java deleted file mode 100644 index c1b65897..00000000 --- a/implementation/core/src/test/java/io/smallrye/faulttolerance/core/util/ExceptionDecisionTest.java +++ /dev/null @@ -1,45 +0,0 @@ -package io.smallrye.faulttolerance.core.util; - -import static org.assertj.core.api.Assertions.assertThat; - -import org.junit.jupiter.api.Test; - -public class ExceptionDecisionTest { - @Test - public void consideredFailure() { - assertThat(ExceptionDecision.ALWAYS_FAILURE.isConsideredExpected(new Exception())).isFalse(); - } - - @Test - public void consideredExpected() { - assertThat(ExceptionDecision.ALWAYS_EXPECTED.isConsideredExpected(new Exception())).isTrue(); - } - - @Test - public void unknown() { - assertThat(ExceptionDecision.EMPTY.isConsideredExpected(new Exception())).isTrue(); - } - - @Test - public void causeConsideredFailure() { - ExceptionDecision decision = new ExceptionDecision(SetOfThrowables.create(TestException.class), - SetOfThrowables.EMPTY, true); - - assertThat(decision.isConsideredExpected(new Exception(new TestException()))).isFalse(); - } - - @Test - public void causeConsideredExpected() { - ExceptionDecision decision = new ExceptionDecision(SetOfThrowables.EMPTY, - SetOfThrowables.create(TestException.class), true); - - assertThat(decision.isConsideredExpected(new Exception(new TestException()))).isTrue(); - } - - @Test - public void causeUnknown() { - ExceptionDecision decision = new ExceptionDecision(SetOfThrowables.EMPTY, SetOfThrowables.EMPTY, true); - - assertThat(decision.isConsideredExpected(new Exception(new TestException()))).isTrue(); - } -} diff --git a/implementation/core/src/test/java/io/smallrye/faulttolerance/core/util/PredicateBasedExceptionDecisionTest.java b/implementation/core/src/test/java/io/smallrye/faulttolerance/core/util/PredicateBasedExceptionDecisionTest.java new file mode 100644 index 00000000..e16efc56 --- /dev/null +++ b/implementation/core/src/test/java/io/smallrye/faulttolerance/core/util/PredicateBasedExceptionDecisionTest.java @@ -0,0 +1,33 @@ +package io.smallrye.faulttolerance.core.util; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.Test; + +public class PredicateBasedExceptionDecisionTest { + @Test + public void consideredFailure() { + ExceptionDecision alwaysFailure = new PredicateBasedExceptionDecision(ignored -> false); + assertThat(alwaysFailure.isConsideredExpected(new Exception())).isFalse(); + } + + @Test + public void consideredExpected() { + ExceptionDecision alwaysExpected = new PredicateBasedExceptionDecision(ignored -> true); + assertThat(alwaysExpected.isConsideredExpected(new Exception())).isTrue(); + } + + @Test + public void causeConsideredFailure() { + ExceptionDecision decision = new PredicateBasedExceptionDecision(e -> !(e.getCause() instanceof TestException)); + + assertThat(decision.isConsideredExpected(new Exception(new TestException()))).isFalse(); + } + + @Test + public void causeConsideredExpected() { + ExceptionDecision decision = new PredicateBasedExceptionDecision(e -> e.getCause() instanceof TestException); + + assertThat(decision.isConsideredExpected(new Exception(new TestException()))).isTrue(); + } +} diff --git a/implementation/core/src/test/java/io/smallrye/faulttolerance/core/util/SetBasedExceptionDecisionTest.java b/implementation/core/src/test/java/io/smallrye/faulttolerance/core/util/SetBasedExceptionDecisionTest.java new file mode 100644 index 00000000..45c51626 --- /dev/null +++ b/implementation/core/src/test/java/io/smallrye/faulttolerance/core/util/SetBasedExceptionDecisionTest.java @@ -0,0 +1,48 @@ +package io.smallrye.faulttolerance.core.util; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.Test; + +public class SetBasedExceptionDecisionTest { + @Test + public void consideredFailure() { + ExceptionDecision alwaysFailure = new SetBasedExceptionDecision(SetOfThrowables.ALL, SetOfThrowables.EMPTY, false); + assertThat(alwaysFailure.isConsideredExpected(new Exception())).isFalse(); + } + + @Test + public void consideredExpected() { + ExceptionDecision alwaysExpected = new SetBasedExceptionDecision(SetOfThrowables.EMPTY, SetOfThrowables.ALL, false); + assertThat(alwaysExpected.isConsideredExpected(new Exception())).isTrue(); + } + + @Test + public void unknown() { + ExceptionDecision empty = new SetBasedExceptionDecision(SetOfThrowables.EMPTY, SetOfThrowables.EMPTY, false); + assertThat(empty.isConsideredExpected(new Exception())).isTrue(); + } + + @Test + public void causeConsideredFailure() { + ExceptionDecision decision = new SetBasedExceptionDecision(SetOfThrowables.create(TestException.class), + SetOfThrowables.EMPTY, true); + + assertThat(decision.isConsideredExpected(new Exception(new TestException()))).isFalse(); + } + + @Test + public void causeConsideredExpected() { + ExceptionDecision decision = new SetBasedExceptionDecision(SetOfThrowables.EMPTY, + SetOfThrowables.create(TestException.class), true); + + assertThat(decision.isConsideredExpected(new Exception(new TestException()))).isTrue(); + } + + @Test + public void causeUnknown() { + ExceptionDecision decision = new SetBasedExceptionDecision(SetOfThrowables.EMPTY, SetOfThrowables.EMPTY, true); + + assertThat(decision.isConsideredExpected(new Exception(new TestException()))).isTrue(); + } +} 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 4584791f..439ca6a9 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 @@ -86,6 +86,7 @@ import io.smallrye.faulttolerance.core.timer.Timer; import io.smallrye.faulttolerance.core.util.DirectExecutor; import io.smallrye.faulttolerance.core.util.ExceptionDecision; +import io.smallrye.faulttolerance.core.util.SetBasedExceptionDecision; import io.smallrye.faulttolerance.core.util.SetOfThrowables; import io.smallrye.faulttolerance.internal.InterceptionPoint; import io.smallrye.faulttolerance.internal.RequestScopeActivator; @@ -533,7 +534,7 @@ private long getTimeInMs(long time, ChronoUnit unit) { private ExceptionDecision createExceptionDecision(Class[] consideredExpected, Class[] consideredFailure) { - return new ExceptionDecision(createSetOfThrowables(consideredFailure), + return new SetBasedExceptionDecision(createSetOfThrowables(consideredFailure), createSetOfThrowables(consideredExpected), specCompatibility.inspectExceptionCauseChain()); } 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 d895868a..ea7036e2 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 @@ -80,6 +80,26 @@ public void circuitBreakerWithFailOn() { .withCauseExactlyInstanceOf(TestException.class); } + @Test + public void circuitBreakerWithWhen() { + Supplier> guarded = MutinyFaultTolerance.createSupplier(this::action) + .withCircuitBreaker().requestVolumeThreshold(4).when(e -> e instanceof RuntimeException).done() + .withFallback().handler(this::fallback).when(e -> e instanceof CircuitBreakerOpenException).done() + .build(); + + for (int i = 0; i < 4; i++) { + assertThat(guarded.get().subscribeAsCompletionStage()) + .failsWithin(10, TimeUnit.SECONDS) + .withThrowableOfType(ExecutionException.class) // caused by AssertJ calling future.get() + .withCauseExactlyInstanceOf(TestException.class); + } + + assertThat(guarded.get().subscribeAsCompletionStage()) + .failsWithin(10, TimeUnit.SECONDS) + .withThrowableOfType(ExecutionException.class) // caused by AssertJ calling future.get() + .withCauseExactlyInstanceOf(TestException.class); + } + public Uni action() { return Uni.createFrom().failure(new TestException()); } 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 5baa2883..3779699f 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 @@ -59,6 +59,18 @@ public void fallbackWithApplyOn() { .withCauseExactlyInstanceOf(TestException.class); } + @Test + public void fallbackWithWhen() { + Supplier> guarded = MutinyFaultTolerance.createSupplier(this::action) + .withFallback().handler(this::fallback).when(e -> e instanceof RuntimeException).done() + .build(); + + assertThat(guarded.get().subscribeAsCompletionStage()) + .failsWithin(10, TimeUnit.SECONDS) + .withThrowableOfType(ExecutionException.class) // caused by AssertJ calling future.get() + .withCauseExactlyInstanceOf(TestException.class); + } + public Uni action() { return Uni.createFrom().failure(new TestException()); } 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 116cdaf1..f30e2106 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 @@ -60,6 +60,19 @@ public void retryWithRetryOn() { assertThat(counter).hasValue(1); // 1 initial invocation } + @Test + public void retryWithWhen() { + Supplier> guarded = MutinyFaultTolerance.createSupplier(this::action) + .withRetry().maxRetries(3).when(e -> e instanceof RuntimeException).done() + .withFallback().handler(this::fallback).when(e -> e instanceof TestException).done() + .build(); + + assertThat(guarded.get().subscribeAsCompletionStage()) + .succeedsWithin(10, TimeUnit.SECONDS) + .isEqualTo("fallback"); + assertThat(counter).hasValue(1); // 1 initial invocation + } + public Uni action() { counter.incrementAndGet(); return Uni.createFrom().failure(new TestException()); 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 44b82fd4..96399250 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 @@ -23,7 +23,7 @@ public void setUp() { } @Test - public void asyncCircuitBreaker() throws Exception { + public void asyncCircuitBreaker() { Supplier> guarded = FaultTolerance.createAsyncSupplier(this::action) .withCircuitBreaker().requestVolumeThreshold(4).done() .withFallback().handler(this::fallback).applyOn(CircuitBreakerOpenException.class).done() @@ -42,7 +42,7 @@ public void asyncCircuitBreaker() throws Exception { } @Test - public void asyncCircuitBreakerWithSkipOn() throws Exception { + public void asyncCircuitBreakerWithSkipOn() { Supplier> guarded = FaultTolerance.createAsyncSupplier(this::action) .withCircuitBreaker().requestVolumeThreshold(4).skipOn(TestException.class).done() .withFallback().handler(this::fallback).applyOn(CircuitBreakerOpenException.class).done() @@ -62,7 +62,7 @@ public void asyncCircuitBreakerWithSkipOn() throws Exception { } @Test - public void asyncCircuitBreakerWithFailOn() throws Exception { + public void asyncCircuitBreakerWithFailOn() { Supplier> guarded = FaultTolerance.createAsyncSupplier(this::action) .withCircuitBreaker().requestVolumeThreshold(4).failOn(RuntimeException.class).done() .withFallback().handler(this::fallback).applyOn(CircuitBreakerOpenException.class).done() @@ -81,6 +81,26 @@ public void asyncCircuitBreakerWithFailOn() throws Exception { .withCauseExactlyInstanceOf(TestException.class); } + @Test + public void asyncCircuitBreakerWithWhen() { + Supplier> guarded = FaultTolerance.createAsyncSupplier(this::action) + .withCircuitBreaker().requestVolumeThreshold(4).when(e -> e instanceof RuntimeException).done() + .withFallback().handler(this::fallback).when(e -> e instanceof CircuitBreakerOpenException).done() + .build(); + + for (int i = 0; i < 4; i++) { + assertThat(guarded.get()) + .failsWithin(10, TimeUnit.SECONDS) + .withThrowableOfType(ExecutionException.class) // caused by AssertJ calling future.get() + .withCauseExactlyInstanceOf(TestException.class); + } + + assertThat(guarded.get()) + .failsWithin(10, TimeUnit.SECONDS) + .withThrowableOfType(ExecutionException.class) // caused by AssertJ calling future.get() + .withCauseExactlyInstanceOf(TestException.class); + } + public CompletionStage action() { return failedStage(new TestException()); } 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 4a03053b..81587c3d 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 @@ -60,6 +60,20 @@ public void circuitBreakerWithFailOn() { assertThatCode(guarded::call).isExactlyInstanceOf(TestException.class); } + @Test + public void circuitBreakerWithWhen() { + Callable guarded = FaultTolerance.createCallable(this::action) + .withCircuitBreaker().requestVolumeThreshold(4).when(e -> e instanceof RuntimeException).done() + .withFallback().handler(this::fallback).when(e -> e instanceof CircuitBreakerOpenException).done() + .build(); + + for (int i = 0; i < 4; i++) { + assertThatCode(guarded::call).isExactlyInstanceOf(TestException.class); + } + + assertThatCode(guarded::call).isExactlyInstanceOf(TestException.class); + } + public String action() throws TestException { throw new TestException(); } 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 66c25fb1..f9325295 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 @@ -61,6 +61,18 @@ public void asyncFallbackWithApplyOn() { .withCauseExactlyInstanceOf(TestException.class); } + @Test + public void asyncFallbackWithWhen() { + Supplier> guarded = FaultTolerance.createAsyncSupplier(this::action) + .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 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 8e408290..4a64adcf 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 @@ -47,6 +47,15 @@ public void fallbackWithApplyOn() { assertThatCode(guarded::call).isExactlyInstanceOf(TestException.class); } + @Test + public void fallbackWithWhen() { + Callable guarded = FaultTolerance.createCallable(this::action) + .withFallback().handler(this::fallback).when(e -> e instanceof RuntimeException).done() + .build(); + + assertThatCode(guarded::call).isExactlyInstanceOf(TestException.class); + } + public String action() throws TestException { throw new TestException(); } 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 cabb3ea2..598342db 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 @@ -63,6 +63,19 @@ public void asyncRetryWithRetryOn() { assertThat(counter).hasValue(1); // 1 initial invocation } + @Test + public void asyncRetryWithWhen() { + Supplier> guarded = FaultTolerance.createAsyncSupplier(this::action) + .withRetry().maxRetries(3).when(e -> e instanceof RuntimeException).done() + .withFallback().handler(this::fallback).when(e -> e instanceof TestException).done() + .build(); + + assertThat(guarded.get()) + .succeedsWithin(10, TimeUnit.SECONDS) + .isEqualTo("fallback"); + assertThat(counter).hasValue(1); // 1 initial invocation + } + @Test public void synchronousFlow() { // this is usually a mistake, because it only guards the synchronous execution 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 a8fc2bda..47f0edba 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 @@ -51,6 +51,17 @@ public void retryWithRetryOn() throws Exception { assertThat(counter).isEqualTo(1); // 1 initial invocation } + @Test + public void retryWithWhen() throws Exception { + Callable guarded = FaultTolerance.createCallable(this::action) + .withRetry().maxRetries(3).when(e -> e instanceof RuntimeException).done() + .withFallback().when(e -> e instanceof TestException).handler(this::fallback).done() + .build(); + + assertThat(guarded.call()).isEqualTo("fallback"); + assertThat(counter).isEqualTo(1); // 1 initial invocation + } + public String action() throws TestException { counter++; throw new TestException(); From 65ddd085c993653acf1b41b946b248331598a22b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 7 Mar 2022 07:13:18 +0000 Subject: [PATCH 49/68] Bump awaitility from 4.1.1 to 4.2.0 Bumps [awaitility](https://github.com/awaitility/awaitility) from 4.1.1 to 4.2.0. - [Release notes](https://github.com/awaitility/awaitility/releases) - [Changelog](https://github.com/awaitility/awaitility/blob/master/changelog.txt) - [Commits](https://github.com/awaitility/awaitility/compare/awaitility-4.1.1...awaitility-4.2.0) --- updated-dependencies: - dependency-name: org.awaitility:awaitility dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 1573b8a5..6fbeee86 100644 --- a/pom.xml +++ b/pom.xml @@ -55,7 +55,7 @@ 1.6.0.Final 2.1.0.Final 3.22.0 - 4.1.1 + 4.2.0 5.8.2 1.6.1 1.3.1 From 85e00497efb79c96221919ca7baeb00e7dcb996f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 11 Mar 2022 07:08:24 +0000 Subject: [PATCH 50/68] Bump mutiny from 1.3.1 to 1.4.0 Bumps [mutiny](https://github.com/smallrye/smallrye-mutiny) from 1.3.1 to 1.4.0. - [Release notes](https://github.com/smallrye/smallrye-mutiny/releases) - [Commits](https://github.com/smallrye/smallrye-mutiny/compare/1.3.1...1.4.0) --- updated-dependencies: - dependency-name: io.smallrye.reactive:mutiny dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 6fbeee86..141d6f9f 100644 --- a/pom.xml +++ b/pom.xml @@ -58,7 +58,7 @@ 4.2.0 5.8.2 1.6.1 - 1.3.1 + 1.4.0 3.1.3 6.14.3 3.1.SP4 From c0b56603fbc924c541c3ee94ea2eec739460c9b7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 17 Mar 2022 07:09:27 +0000 Subject: [PATCH 51/68] Bump vertx-core from 4.2.5 to 4.2.6 Bumps [vertx-core](https://github.com/eclipse/vert.x) from 4.2.5 to 4.2.6. - [Release notes](https://github.com/eclipse/vert.x/releases) - [Commits](https://github.com/eclipse/vert.x/compare/4.2.5...4.2.6) --- updated-dependencies: - dependency-name: io.vertx:vertx-core dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 141d6f9f..0bba3a4d 100644 --- a/pom.xml +++ b/pom.xml @@ -50,7 +50,7 @@ 1.10.0 2.6.0 0.33.0 - 4.2.5 + 4.2.6 1.6.0.Final 2.1.0.Final From 173f19211f36560a0b20db56851995b6433a8e85 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 17 Mar 2022 08:02:25 +0000 Subject: [PATCH 52/68] Bump smallrye-config from 2.9.1 to 2.9.2 Bumps [smallrye-config](https://github.com/smallrye/smallrye-config) from 2.9.1 to 2.9.2. - [Release notes](https://github.com/smallrye/smallrye-config/releases) - [Commits](https://github.com/smallrye/smallrye-config/compare/2.9.1...2.9.2) --- updated-dependencies: - dependency-name: io.smallrye.config:smallrye-config dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 0bba3a4d..0e79c7f0 100644 --- a/pom.xml +++ b/pom.xml @@ -42,7 +42,7 @@ 3.0 1.2 - 2.9.1 + 2.9.2 3.0.4 From b3933971454ebf014c618971eee6d30927a45589 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 17 Mar 2022 08:35:53 +0000 Subject: [PATCH 53/68] Bump junit-pioneer from 1.6.1 to 1.6.2 Bumps [junit-pioneer](https://github.com/junit-pioneer/junit-pioneer) from 1.6.1 to 1.6.2. - [Release notes](https://github.com/junit-pioneer/junit-pioneer/releases) - [Commits](https://github.com/junit-pioneer/junit-pioneer/compare/v1.6.1...v1.6.2) --- updated-dependencies: - dependency-name: org.junit-pioneer:junit-pioneer dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 0e79c7f0..136dcef6 100644 --- a/pom.xml +++ b/pom.xml @@ -57,7 +57,7 @@ 3.22.0 4.2.0 5.8.2 - 1.6.1 + 1.6.2 1.4.0 3.1.3 6.14.3 From 91196a335aa7892fc7ce63dc9b17d9784a1b0630 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 22 Mar 2022 07:09:09 +0000 Subject: [PATCH 54/68] Bump rxjava from 3.1.3 to 3.1.4 Bumps [rxjava](https://github.com/ReactiveX/RxJava) from 3.1.3 to 3.1.4. - [Release notes](https://github.com/ReactiveX/RxJava/releases) - [Commits](https://github.com/ReactiveX/RxJava/compare/v3.1.3...v3.1.4) --- updated-dependencies: - dependency-name: io.reactivex.rxjava3:rxjava dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 136dcef6..cc31c0e0 100644 --- a/pom.xml +++ b/pom.xml @@ -59,7 +59,7 @@ 5.8.2 1.6.2 1.4.0 - 3.1.3 + 3.1.4 6.14.3 3.1.SP4 3.1.8.Final From b80956c2a3530841133ec7ea3a741d8ac08cddb4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 24 Mar 2022 07:10:19 +0000 Subject: [PATCH 55/68] Bump pitest-maven from 1.7.4 to 1.7.5 Bumps [pitest-maven](https://github.com/hcoles/pitest) from 1.7.4 to 1.7.5. - [Release notes](https://github.com/hcoles/pitest/releases) - [Commits](https://github.com/hcoles/pitest/compare/1.7.4...1.7.5) --- updated-dependencies: - dependency-name: org.pitest:pitest-maven dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- implementation/core/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/implementation/core/pom.xml b/implementation/core/pom.xml index fceb2b78..65e0840f 100644 --- a/implementation/core/pom.xml +++ b/implementation/core/pom.xml @@ -35,7 +35,7 @@ 2.22.1 0.8.6 - 1.7.4 + 1.7.5 0.15 From 707e19aa3d7f381bb8a6c7beb28d8afa208345a0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 28 Mar 2022 07:15:59 +0000 Subject: [PATCH 56/68] Bump smallrye-common-annotation from 1.10.0 to 1.11.0 Bumps [smallrye-common-annotation](https://github.com/smallrye/smallrye-common) from 1.10.0 to 1.11.0. - [Release notes](https://github.com/smallrye/smallrye-common/releases) - [Commits](https://github.com/smallrye/smallrye-common/compare/1.10.0...1.11.0) --- updated-dependencies: - dependency-name: io.smallrye.common:smallrye-common-annotation dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index cc31c0e0..aff6d543 100644 --- a/pom.xml +++ b/pom.xml @@ -47,7 +47,7 @@ 3.0.4 1.2.2 - 1.10.0 + 1.11.0 2.6.0 0.33.0 4.2.6 From d440d3445a14050b7b5f4bf464b3fc50c24c897a Mon Sep 17 00:00:00 2001 From: Ladislav Thon Date: Fri, 25 Mar 2022 15:07:49 +0100 Subject: [PATCH 57/68] add support for Kotlin suspending functions This requires big refactoring of the async types support facility. Previously, the core unwritten assumption was that async functions always return an async type (such as `CompletionStage`). This allowed implementing conversion between the async type and the internal `CompletionStage`-based representation relatively easily, as a pair of `FaultToleranceStrategy`-ies that simply act on the return values. With Kotlin suspending functions, that is no longer possible. These functions are compiled to methods that have one extra additional parameter of type `Continuation`, where `T` is the original return type of the function, and their return type becomes `Object`, which actually stands for a non-denotable union type of `T` and a special marker object `COROUTINE_SUSPENDED`. If the suspending function never suspends, it simply returns the result, but whenever it does suspend, it returns `COROUTINE_SUSPENDED` and delivers the result later, via the `Continuation` parameter. To be able to translate between this representation and the internal representation based on `CompletionStage`, a more elaborate scheme is required. This means that the existing strategies that perform type conversions are removed, together with the existing conversion code. (The strategies were never type safe, so in that sense, removing them is actually a good thing.) Instead, a dedicated facility for async conversions on both ends of the strategy chain is added, called `AsyncSupport`. It serves dual purpose: to convert the target invocation into an invocation that returns a `CompletionStage`, and to convert an invocation that returns a `CompletionStage` to an invocation that returns the result expected by the caller. For each such conversion, `AsyncSupport` is given an `Invoker`, which allows, besides just proceeding with the invocation and acting on the result, also accessing the arguments that shall be passed to the method when it is invoked. This is general enough to support both the previous conversion scheme (based just on return values), as well as the Kotlin suspending functions scheme. Supporting suspending functions is simple if they never suspend. In such case, when invoking the target, it is enough to take the return value (or thrown exception), wrap it into a completed `CompletableFuture`, and pass it back to the strategy chain. On the other side of the chain, when returning the result to the caller, it is enough to obtain the result from the `CompletionStage` and return/rethrow it. It gets more tricky in case of functions that actually suspend. When invoking the target, it is necessary to replace the `Continuation` argument with a special one that completes a `CompletableFuture`, and pass that `CompletableFuture` back to the strategy chain. On the other side of the chain, when returning a result to the caller, it is necessary to attach a callback to the `CompletionStage`, which completes the original continuation. With that callback in place, returning `COROUTINE_SUSPENDED` is all that's left. Support for Kotlin suspending functions is only present in the declarative, annotation-based API. The programmatic API can also be used from Kotlin, but it doesn't support suspending functions (or suspending lambdas, actually). This could possibly be added in the future, though it would likely require some more refactoring. --- doc/modules/ROOT/nav.adoc | 1 + .../ROOT/pages/integration/kotlin.adoc | 9 ++ doc/modules/ROOT/pages/usage/extra.adoc | 64 +++++++++-- .../autoconfig/FaultToleranceMethod.java | 4 + .../autoconfig/KotlinSupport.java | 13 +++ .../core/apiimpl/CallableInvoker.java | 34 ++++++ .../core/apiimpl/FaultToleranceImpl.java | 85 +++++++++----- .../core/async/types/AsyncTypeConverter.java | 12 -- .../core/async/types/AsyncTypes.java | 50 --------- .../async/types/AsyncTypesConversion.java | 56 --------- .../core/async/types/AsyncTypesLogger.java | 10 -- .../async/types/CompletionStageConverter.java | 21 ---- .../core/invocation/AsyncSupport.java | 21 ++++ .../core/invocation/AsyncSupportRegistry.java | 50 +++++++++ .../invocation/CompletionStageSupport.java | 38 +++++++ .../core/invocation/Invoker.java | 14 +++ .../core/invocation/NormalMethodInvoker.java | 50 +++++++++ .../core/invocation/SpecialMethodInvoker.java | 64 +++++++++++ .../core/invocation/StrategyInvoker.java | 44 ++++++++ .../faulttolerance/core/util/Initializer.java | 13 ++- .../core/invocation/SpecialMethodInvoker.java | 63 +++++++++++ ...erance.core.async.types.AsyncTypeConverter | 1 - ...aulttolerance.core.invocation.AsyncSupport | 1 + .../DefaultMethodFallbackProvider.java | 29 ----- .../FaultToleranceInterceptor.java | 98 ++++++++-------- .../faulttolerance/SpecCompatibility.java | 9 +- .../config/AsyncValidation.java | 21 ---- .../config/AsynchronousConfig.java | 19 +++- .../faulttolerance/config/FallbackConfig.java | 5 + .../config/FaultToleranceOperation.java | 4 + .../faulttolerance/config/KotlinSupport.java | 34 ++++++ .../internal/InterceptionInvoker.java | 40 +++++++ .../DefaultMethodFallbackProvider.java | 27 ----- implementation/kotlin/pom.xml | 106 ++++++++++++++++++ .../kotlin/impl/CoroutineSupport.kt | 95 ++++++++++++++++ ...aulttolerance.core.invocation.AsyncSupport | 1 + .../mutiny/impl/UniConverter.java | 24 ---- .../mutiny/impl/UniSupport.java | 44 ++++++++ ...erance.core.async.types.AsyncTypeConverter | 1 - ...aulttolerance.core.invocation.AsyncSupport | 1 + implementation/pom.xml | 2 + .../rxjava3/impl/CompletableConverter.java | 24 ---- .../rxjava3/impl/CompletableSupport.java | 34 ++++++ .../rxjava3/impl/MaybeConverter.java | 24 ---- .../rxjava3/impl/MaybeSupport.java | 34 ++++++ .../rxjava3/impl/SingleConverter.java | 24 ---- .../rxjava3/impl/SingleSupport.java | 34 ++++++ ...erance.core.async.types.AsyncTypeConverter | 3 - ...aulttolerance.core.invocation.AsyncSupport | 3 + pom.xml | 60 ++++++---- testsuite/basic/pom.xml | 76 +++++++++++++ .../kotlin/bulkhead/KotlinBulkheadTest.kt | 36 ++++++ .../kotlin/bulkhead/MyService.kt | 29 +++++ .../KotlinCircuitBreakerTest.kt | 38 +++++++ .../kotlin/circuitbreaker/MyService.kt | 27 +++++ .../kotlin/retry/KotlinRetryTest.kt | 19 ++++ .../faulttolerance/kotlin/retry/MyService.kt | 28 +++++ .../kotlin/timeout/KotlinTimeoutTest.kt | 18 +++ .../kotlin/timeout/MyService.kt | 22 ++++ 59 files changed, 1364 insertions(+), 447 deletions(-) create mode 100644 doc/modules/ROOT/pages/integration/kotlin.adoc create mode 100644 implementation/autoconfig/core/src/main/java/io/smallrye/faulttolerance/autoconfig/KotlinSupport.java create mode 100644 implementation/core/src/main/java/io/smallrye/faulttolerance/core/apiimpl/CallableInvoker.java delete mode 100644 implementation/core/src/main/java/io/smallrye/faulttolerance/core/async/types/AsyncTypeConverter.java delete mode 100644 implementation/core/src/main/java/io/smallrye/faulttolerance/core/async/types/AsyncTypes.java delete mode 100644 implementation/core/src/main/java/io/smallrye/faulttolerance/core/async/types/AsyncTypesConversion.java delete mode 100644 implementation/core/src/main/java/io/smallrye/faulttolerance/core/async/types/AsyncTypesLogger.java delete mode 100644 implementation/core/src/main/java/io/smallrye/faulttolerance/core/async/types/CompletionStageConverter.java create mode 100644 implementation/core/src/main/java/io/smallrye/faulttolerance/core/invocation/AsyncSupport.java create mode 100644 implementation/core/src/main/java/io/smallrye/faulttolerance/core/invocation/AsyncSupportRegistry.java create mode 100644 implementation/core/src/main/java/io/smallrye/faulttolerance/core/invocation/CompletionStageSupport.java create mode 100644 implementation/core/src/main/java/io/smallrye/faulttolerance/core/invocation/Invoker.java create mode 100644 implementation/core/src/main/java/io/smallrye/faulttolerance/core/invocation/NormalMethodInvoker.java create mode 100644 implementation/core/src/main/java/io/smallrye/faulttolerance/core/invocation/SpecialMethodInvoker.java create mode 100644 implementation/core/src/main/java/io/smallrye/faulttolerance/core/invocation/StrategyInvoker.java create mode 100644 implementation/core/src/main/java9/io/smallrye/faulttolerance/core/invocation/SpecialMethodInvoker.java delete mode 100644 implementation/core/src/main/resources/META-INF/services/io.smallrye.faulttolerance.core.async.types.AsyncTypeConverter create mode 100644 implementation/core/src/main/resources/META-INF/services/io.smallrye.faulttolerance.core.invocation.AsyncSupport delete mode 100644 implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/DefaultMethodFallbackProvider.java delete mode 100644 implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/config/AsyncValidation.java create mode 100644 implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/config/KotlinSupport.java create mode 100644 implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/internal/InterceptionInvoker.java delete mode 100644 implementation/fault-tolerance/src/main/java9/io/smallrye/faulttolerance/DefaultMethodFallbackProvider.java create mode 100644 implementation/kotlin/pom.xml create mode 100644 implementation/kotlin/src/main/kotlin/io/smallrye/faulttolerance/kotlin/impl/CoroutineSupport.kt create mode 100644 implementation/kotlin/src/main/resources/META-INF/services/io.smallrye.faulttolerance.core.invocation.AsyncSupport delete mode 100644 implementation/mutiny/src/main/java/io/smallrye/faulttolerance/mutiny/impl/UniConverter.java create mode 100644 implementation/mutiny/src/main/java/io/smallrye/faulttolerance/mutiny/impl/UniSupport.java delete mode 100644 implementation/mutiny/src/main/resources/META-INF/services/io.smallrye.faulttolerance.core.async.types.AsyncTypeConverter create mode 100644 implementation/mutiny/src/main/resources/META-INF/services/io.smallrye.faulttolerance.core.invocation.AsyncSupport delete mode 100644 implementation/rxjava3/src/main/java/io/smallrye/faulttolerance/rxjava3/impl/CompletableConverter.java create mode 100644 implementation/rxjava3/src/main/java/io/smallrye/faulttolerance/rxjava3/impl/CompletableSupport.java delete mode 100644 implementation/rxjava3/src/main/java/io/smallrye/faulttolerance/rxjava3/impl/MaybeConverter.java create mode 100644 implementation/rxjava3/src/main/java/io/smallrye/faulttolerance/rxjava3/impl/MaybeSupport.java delete mode 100644 implementation/rxjava3/src/main/java/io/smallrye/faulttolerance/rxjava3/impl/SingleConverter.java create mode 100644 implementation/rxjava3/src/main/java/io/smallrye/faulttolerance/rxjava3/impl/SingleSupport.java delete mode 100644 implementation/rxjava3/src/main/resources/META-INF/services/io.smallrye.faulttolerance.core.async.types.AsyncTypeConverter create mode 100644 implementation/rxjava3/src/main/resources/META-INF/services/io.smallrye.faulttolerance.core.invocation.AsyncSupport create mode 100644 testsuite/basic/src/test/kotlin/io/smallrye/faulttolerance/kotlin/bulkhead/KotlinBulkheadTest.kt create mode 100644 testsuite/basic/src/test/kotlin/io/smallrye/faulttolerance/kotlin/bulkhead/MyService.kt create mode 100644 testsuite/basic/src/test/kotlin/io/smallrye/faulttolerance/kotlin/circuitbreaker/KotlinCircuitBreakerTest.kt create mode 100644 testsuite/basic/src/test/kotlin/io/smallrye/faulttolerance/kotlin/circuitbreaker/MyService.kt create mode 100644 testsuite/basic/src/test/kotlin/io/smallrye/faulttolerance/kotlin/retry/KotlinRetryTest.kt create mode 100644 testsuite/basic/src/test/kotlin/io/smallrye/faulttolerance/kotlin/retry/MyService.kt create mode 100644 testsuite/basic/src/test/kotlin/io/smallrye/faulttolerance/kotlin/timeout/KotlinTimeoutTest.kt create mode 100644 testsuite/basic/src/test/kotlin/io/smallrye/faulttolerance/kotlin/timeout/MyService.kt diff --git a/doc/modules/ROOT/nav.adoc b/doc/modules/ROOT/nav.adoc index ed0df699..b2a6f717 100644 --- a/doc/modules/ROOT/nav.adoc +++ b/doc/modules/ROOT/nav.adoc @@ -10,6 +10,7 @@ ** xref:integration/opentracing.adoc[OpenTracing] ** xref:integration/event-loop.adoc[Event Loop] ** xref:integration/async-types.adoc[Additional Asynchronous Types Integration Concerns] +** xref:integration/kotlin.adoc[Kotlin Integration Concerns] ** xref:integration/programmatic-api.adoc[Programmatic API Integration Concerns] * Internals ** xref:internals/project-structure.adoc[Project Structure] diff --git a/doc/modules/ROOT/pages/integration/kotlin.adoc b/doc/modules/ROOT/pages/integration/kotlin.adoc new file mode 100644 index 00000000..27be1ce6 --- /dev/null +++ b/doc/modules/ROOT/pages/integration/kotlin.adoc @@ -0,0 +1,9 @@ += Kotlin Integration Concerns + +This page describes integration concerns for xref:usage/extra.adoc#kotlin-suspend-functions[Kotlin `suspend` functions]. + +To enable support of Kotlin suspending functions, it is required that the support library is present. +Its coordinates are: `io.smallrye:smallrye-fault-tolerance-kotlin`. + +This library includes some service providers that {smallrye-fault-tolerance} will automatically load and use. +Therefore, no more integration is necessary. diff --git a/doc/modules/ROOT/pages/usage/extra.adoc b/doc/modules/ROOT/pages/usage/extra.adoc index c16316a4..f54b02ec 100644 --- a/doc/modules/ROOT/pages/usage/extra.adoc +++ b/doc/modules/ROOT/pages/usage/extra.adoc @@ -198,7 +198,7 @@ public class MyService { <1> Using the `@NonBlocking` annotation described in <>, because the method doesn't block and offloading execution to another thread is not necessary. <2> Returning the `Uni` type from Mutiny. - This shows that whatever works for `CompletionStage` also works for the other async types. +This shows that whatever works for `CompletionStage` also works for the other async types. The implementation internally converts the async types to a `CompletionStage` and back. This means that to be able to use any particular asynchronous type, the corresponding converter must be present. @@ -311,17 +311,16 @@ Note that the non-compatible mode is available since {smallrye-fault-tolerance} Previous versions are always compatible. **** -=== Determining Method Asynchrony from Return Type +[[method-asynchrony]] +=== Determining Asynchrony from Method Signature -In addition to the <> annotations, in the non-compatible mode, method asynchrony is determined solely from its return type. +In the non-compatible mode, method asynchrony is determined solely from its signature. That is, methods that * have some fault tolerance annotation (such as `@Retry`), * return `CompletionStage` (or some other <>), -* are _not_ annotated `@Blocking`, `@NonBlocking` or `@Asynchronous` -are not offloaded to a thread pool and still have asynchronous fault tolerance applied. -(In other words, they are treated as if they were annotated `@NonBlocking`.) +always have asynchronous fault tolerance applied. For example: @@ -359,7 +358,7 @@ That is, if a method (or class) is annotated `@Asynchronous` or `@Blocking`, exe If a method (or class) is annotated `@NonBlocking`, execution will happen on the original thread (even if `@Asynchronous` is present). Also note that this doesn't affect methods returning `Future`. -You still have to annotate them `@Asynchronous` to make sure they are executed on a thread pool. +You still have to annotate them `@Asynchronous` to make sure they are executed on a thread pool and are guarded properly. As mentioned in the <> section, we discourage using these methods, because the only way to obtain the future value is blocking. === Inspecting Exception Cause Chains @@ -420,3 +419,54 @@ All 3 annotations follow the same principle: exceptions considered success have | `abortOn` | `retryOn` |=== + +[[kotlin-suspend-functions]] +== Kotlin `suspend` Functions + +{smallrye-fault-tolerance} includes support for Kotlin suspending functions. +They are treated as <>, even though the internal implementation is more complex than support for Mutiny or RxJava. + +For example: + +[source,kotlin] +---- +@ApplicationScoped +open class MyService { + @Retry(maxRetries = 2) + @Fallback(fallbackMethod = "helloFallback") + open suspend fun hello(): String { // <1> + delay(100) + throw IllegalArgumentException() + } + + private suspend fun helloFallback(): String { // <2> + delay(100) + return "hello" + } +} +---- + +<1> As a suspending function, this method can only be called from another suspending function. +It will be guarded by the retry and fallback strategies, as defined using the annotations. +<2> Similarly to fallback methods in Java, fallback methods in Kotlin must have the same signature as the guarded method. +Since the guarded method is suspending, the fallback method must be suspending. + +As mentioned above, suspending functions are treated as async types. +This means that for asynchronous fault tolerance to work correctly on suspending functions, they must be determined to be asynchronous. +That happens automatically in the <>, based on the method signature, but if you use strictly compatible mode, one of the usual annotations (`@Blocking`, `@NonBlocking`, `@Asynchronous`) must be present. +It is expected that most users will use the Kotlin support in the non-compatible mode, so the example above does not include any such annotation. + +To be able to use this, a support library must be present. +It is possible that the runtime you use already provides the correct integration. +Otherwise, add a dependency to your application: `io.smallrye:smallrye-fault-tolerance-kotlin`. + +.Quarkus +**** +In Quarkus, the Kotlin support library is present by default, if you use the Quarkus Kotlin support. +You can declare fault tolerance annotations on suspending methods out of the box. +**** + +=== Programmatic API + +Suspending functions are currently only supported in the declarative, annotation-based API, as shown in the example above. +The xref:usage/programmatic-api.adoc[Programmatic API] of {smallrye-fault-tolerance} does not support suspending functions, but other than that, it can of course be used from Kotlin through its Java interop. 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 b61abbc4..740e4f2d 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 @@ -59,6 +59,10 @@ public class FaultToleranceMethod { public Set> annotationsPresentDirectly; public boolean isLegitimate() { + if (!KotlinSupport.isLegitimate(method)) { + return false; + } + // SmallRye annotations (@CircuitBreakerName, @[Non]Blocking, @*Backoff) // alone do _not_ trigger the fault tolerance interceptor, // only in combination with other fault tolerance annotations diff --git a/implementation/autoconfig/core/src/main/java/io/smallrye/faulttolerance/autoconfig/KotlinSupport.java b/implementation/autoconfig/core/src/main/java/io/smallrye/faulttolerance/autoconfig/KotlinSupport.java new file mode 100644 index 00000000..08f49a6d --- /dev/null +++ b/implementation/autoconfig/core/src/main/java/io/smallrye/faulttolerance/autoconfig/KotlinSupport.java @@ -0,0 +1,13 @@ +package io.smallrye.faulttolerance.autoconfig; + +// TODO this would ideally live in the `kotlin` module +final class KotlinSupport { + static boolean isLegitimate(MethodDescriptor method) { + if (method.parameterTypes.length > 0 + && method.parameterTypes[method.parameterTypes.length - 1].getName().equals("kotlin.coroutines.Continuation") + && method.name.endsWith("$suspendImpl")) { + return false; + } + return true; + } +} diff --git a/implementation/core/src/main/java/io/smallrye/faulttolerance/core/apiimpl/CallableInvoker.java b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/apiimpl/CallableInvoker.java new file mode 100644 index 00000000..87ca07f6 --- /dev/null +++ b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/apiimpl/CallableInvoker.java @@ -0,0 +1,34 @@ +package io.smallrye.faulttolerance.core.apiimpl; + +import java.util.concurrent.Callable; +import java.util.function.Function; + +import io.smallrye.faulttolerance.core.invocation.Invoker; + +final class CallableInvoker implements Invoker { + private final Callable callable; + + CallableInvoker(Callable callable) { + this.callable = callable; + } + + @Override + public int parametersCount() { + throw new UnsupportedOperationException(); + } + + @Override + public T getArgument(int index, Class parameterType) { + throw new UnsupportedOperationException(); + } + + @Override + public T replaceArgument(int index, Class parameterType, Function transformation) { + throw new UnsupportedOperationException(); + } + + @Override + public V proceed() throws Exception { + return callable.call(); + } +} 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 035e67a6..2bb6c102 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 @@ -17,6 +17,8 @@ import java.util.function.Predicate; import java.util.function.Supplier; +import org.eclipse.microprofile.faulttolerance.exceptions.FaultToleranceException; + import io.smallrye.faulttolerance.api.CircuitBreakerState; import io.smallrye.faulttolerance.api.CustomBackoffStrategy; import io.smallrye.faulttolerance.api.FaultTolerance; @@ -24,8 +26,6 @@ import io.smallrye.faulttolerance.core.InvocationContext; import io.smallrye.faulttolerance.core.async.CompletionStageExecution; import io.smallrye.faulttolerance.core.async.RememberEventLoop; -import io.smallrye.faulttolerance.core.async.types.AsyncTypes; -import io.smallrye.faulttolerance.core.async.types.AsyncTypesConversion; import io.smallrye.faulttolerance.core.bulkhead.CompletionStageThreadPoolBulkhead; import io.smallrye.faulttolerance.core.bulkhead.SemaphoreBulkhead; import io.smallrye.faulttolerance.core.circuit.breaker.CircuitBreaker; @@ -35,6 +35,10 @@ import io.smallrye.faulttolerance.core.fallback.CompletionStageFallback; import io.smallrye.faulttolerance.core.fallback.Fallback; import io.smallrye.faulttolerance.core.fallback.FallbackFunction; +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.retry.BackOff; import io.smallrye.faulttolerance.core.retry.CompletionStageRetry; import io.smallrye.faulttolerance.core.retry.ConstantBackOff; @@ -59,8 +63,15 @@ import io.smallrye.faulttolerance.core.util.SetBasedExceptionDecision; import io.smallrye.faulttolerance.core.util.SetOfThrowables; -public final class FaultToleranceImpl implements FaultTolerance { - private final FaultToleranceStrategy strategy; +// V = value type, e.g. String +// S = strategy type, e.g. String or CompletionStage +// T = result type, e.g. String or CompletionStage or Uni +// +// in synchronous scenario, V = S = T +// in asynchronous scenario, S = CompletionStage and T is an async type that eventually produces V +public final class FaultToleranceImpl implements FaultTolerance { + private final FaultToleranceStrategy strategy; + private final AsyncSupport asyncSupport; private final EventHandlers eventHandlers; private final Initializer initializer; @@ -82,8 +93,10 @@ public final class FaultToleranceImpl implements FaultTolerance { // but that instance is never used. The useful `FaultTolerance` instance is held by the actual bean instance, // which is created lazily, on the first method invocation on the client proxy. - FaultToleranceImpl(FaultToleranceStrategy strategy, EventHandlers eventHandlers, Initializer initializer) { + FaultToleranceImpl(FaultToleranceStrategy strategy, AsyncSupport asyncSupport, EventHandlers eventHandlers, + Initializer initializer) { this.strategy = strategy; + this.asyncSupport = asyncSupport; this.eventHandlers = eventHandlers; this.initializer = initializer; } @@ -92,9 +105,18 @@ public final class FaultToleranceImpl implements FaultTolerance { public T call(Callable action) throws Exception { initializer.runOnce(); - InvocationContext ctx = new InvocationContext<>(action); + if (asyncSupport == null) { + InvocationContext ctx = new InvocationContext<>(action); + eventHandlers.register(ctx); + return ((FaultToleranceStrategy) strategy).apply(ctx); + } + + Invoker invoker = new CallableInvoker<>(action); + InvocationContext> ctx = new InvocationContext<>(() -> asyncSupport.toCompletionStage(invoker)); eventHandlers.register(ctx); - return strategy.apply(ctx); + Invoker> wrapper = new StrategyInvoker<>(null, + (FaultToleranceStrategy>) strategy, ctx); + return asyncSupport.fromCompletionStage(wrapper); } public static final class BuilderImpl implements Builder { @@ -192,10 +214,23 @@ public R build() { timeoutBuilder != null ? timeoutBuilder.onTimeout : null, timeoutBuilder != null ? timeoutBuilder.onFinished : null); + return isAsync ? buildAsync(eventHandlers) : buildSync(eventHandlers); + } + + private R buildSync(EventHandlers eventHandlers) { List initActions = new ArrayList<>(); - FaultToleranceStrategy strategy = isAsync ? buildAsyncStrategy(initActions) : buildSyncStrategy(initActions); + FaultToleranceStrategy strategy = buildSyncStrategy(initActions); + FaultTolerance result = new FaultToleranceImpl<>(strategy, (AsyncSupport) null, eventHandlers, + new Initializer(initActions)); + return finisher.apply(result); + } - FaultTolerance result = new FaultToleranceImpl<>(strategy, eventHandlers, new Initializer(initActions)); + private R buildAsync(EventHandlers eventHandlers) { + List initActions = new ArrayList<>(); + FaultToleranceStrategy> strategy = buildAsyncStrategy(initActions); + AsyncSupport asyncSupport = AsyncSupportRegistry.get(new Class[0], asyncType); + FaultTolerance result = new FaultToleranceImpl<>(strategy, asyncSupport, eventHandlers, + new Initializer(initActions)); return finisher.apply(result); } @@ -251,26 +286,8 @@ private FaultToleranceStrategy buildSyncStrategy(List initActions) return result; } - private FaultToleranceStrategy buildAsyncStrategy(List initActions) { - // FaultToleranceStrategy expects that the input type and output type are the same, which - // isn't true for the conversion strategies used below (even though the entire chain does - // retain the type, the conversions are only intermediate) - // that's why we use raw types here, to work around this design choice - - FaultToleranceStrategy result = invocation(); - - result = new AsyncTypesConversion.ToCompletionStage(result, AsyncTypes.get(asyncType)); - - result = buildCompletionStageChain(result, initActions); - - result = new AsyncTypesConversion.FromCompletionStage(result, AsyncTypes.get(asyncType)); - - return result; - } - - private FaultToleranceStrategy> buildCompletionStageChain( - FaultToleranceStrategy> invocation, List initActions) { - FaultToleranceStrategy> result = invocation; + private FaultToleranceStrategy> buildAsyncStrategy(List initActions) { + FaultToleranceStrategy> result = invocation(); // thread offload is always enabled Executor executor = offloadToAnotherThread ? this.executor : DirectExecutor.INSTANCE; @@ -317,8 +334,14 @@ private FaultToleranceStrategy> buildCompletionStageChain( // fallback is always enabled if (fallbackBuilder != null) { - FallbackFunction> fallbackFunction = ctx -> AsyncTypes.toCompletionStageIfRequired( - fallbackBuilder.handler.apply(ctx.failure), asyncType); + AsyncSupport asyncSupport = AsyncSupportRegistry.get(new Class[0], asyncType); + if (asyncSupport == null) { + throw new FaultToleranceException("Unknown async type: " + asyncType); + } + + FallbackFunction> fallbackFunction = ctx -> asyncSupport.fallbackResultToCompletionStage( + fallbackBuilder.handler.apply(ctx.failure)); + result = new CompletionStageFallback<>(result, description, fallbackFunction, createExceptionDecision(fallbackBuilder.skipOn, fallbackBuilder.applyOn, fallbackBuilder.whenPredicate)); diff --git a/implementation/core/src/main/java/io/smallrye/faulttolerance/core/async/types/AsyncTypeConverter.java b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/async/types/AsyncTypeConverter.java deleted file mode 100644 index 98a02562..00000000 --- a/implementation/core/src/main/java/io/smallrye/faulttolerance/core/async/types/AsyncTypeConverter.java +++ /dev/null @@ -1,12 +0,0 @@ -package io.smallrye.faulttolerance.core.async.types; - -import java.util.concurrent.CompletionStage; -import java.util.function.Supplier; - -public interface AsyncTypeConverter { - Class type(); - - AT fromCompletionStage(Supplier> completionStageSupplier); - - CompletionStage toCompletionStage(AT asyncValue); -} diff --git a/implementation/core/src/main/java/io/smallrye/faulttolerance/core/async/types/AsyncTypes.java b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/async/types/AsyncTypes.java deleted file mode 100644 index a24f6013..00000000 --- a/implementation/core/src/main/java/io/smallrye/faulttolerance/core/async/types/AsyncTypes.java +++ /dev/null @@ -1,50 +0,0 @@ -package io.smallrye.faulttolerance.core.async.types; - -import java.security.AccessController; -import java.security.PrivilegedAction; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import java.util.ServiceLoader; - -public class AsyncTypes { - private static final Map, AsyncTypeConverter> registry; - - static { - Map, AsyncTypeConverter> map = new HashMap<>(); - Iterable converters = (System.getSecurityManager() != null) - ? AccessController.doPrivileged((PrivilegedAction>) AsyncTypes::loadConverters) - : loadConverters(); - for (AsyncTypeConverter converter : converters) { - map.put(converter.type(), converter); - } - registry = Collections.unmodifiableMap(map); - } - - private static ServiceLoader loadConverters() { - return ServiceLoader.load(AsyncTypeConverter.class, AsyncTypeConverter.class.getClassLoader()); - } - - public static boolean isKnown(Class type) { - return registry.containsKey(type); - } - - public static AsyncTypeConverter get(Class type) { - return registry.get(type); - } - - @SuppressWarnings({ "rawtypes", "unchecked" }) - public static T toCompletionStageIfRequired(Object value, Class type) { - AsyncTypeConverter converter = registry.get(type); - if (converter != null) { - return (T) converter.toCompletionStage(value); - } else { - return (T) value; - } - } - - public static Collection> allKnown() { - return registry.values(); - } -} diff --git a/implementation/core/src/main/java/io/smallrye/faulttolerance/core/async/types/AsyncTypesConversion.java b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/async/types/AsyncTypesConversion.java deleted file mode 100644 index 9b779b92..00000000 --- a/implementation/core/src/main/java/io/smallrye/faulttolerance/core/async/types/AsyncTypesConversion.java +++ /dev/null @@ -1,56 +0,0 @@ -package io.smallrye.faulttolerance.core.async.types; - -import static io.smallrye.faulttolerance.core.async.types.AsyncTypesLogger.LOG; -import static io.smallrye.faulttolerance.core.util.SneakyThrow.sneakyThrow; - -import io.smallrye.faulttolerance.core.FaultToleranceStrategy; -import io.smallrye.faulttolerance.core.InvocationContext; - -@SuppressWarnings({ "rawtypes", "unchecked" }) -public class AsyncTypesConversion { - public static class ToCompletionStage implements FaultToleranceStrategy { - private final FaultToleranceStrategy delegate; - private final AsyncTypeConverter converter; - - public ToCompletionStage(FaultToleranceStrategy delegate, AsyncTypeConverter converter) { - this.delegate = delegate; - this.converter = converter; - } - - @Override - public Object apply(InvocationContext ctx) throws Exception { - LOG.trace("AsyncTypesConversion.ToCompletionStage started"); - try { - return converter.toCompletionStage(delegate.apply(ctx)); - } finally { - LOG.trace("AsyncTypesConversion.ToCompletionStage finished"); - } - } - } - - public static class FromCompletionStage implements FaultToleranceStrategy { - private final FaultToleranceStrategy delegate; - private final AsyncTypeConverter converter; - - public FromCompletionStage(FaultToleranceStrategy delegate, AsyncTypeConverter converter) { - this.delegate = delegate; - this.converter = converter; - } - - @Override - public Object apply(InvocationContext ctx) throws Exception { - LOG.trace("AsyncTypesConversion.FromCompletionStage started"); - try { - return converter.fromCompletionStage(() -> { - try { - return delegate.apply(ctx); - } catch (Exception e) { - throw sneakyThrow(e); - } - }); - } finally { - LOG.trace("AsyncTypesConversion.FromCompletionStage finished"); - } - } - } -} diff --git a/implementation/core/src/main/java/io/smallrye/faulttolerance/core/async/types/AsyncTypesLogger.java b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/async/types/AsyncTypesLogger.java deleted file mode 100644 index c8a45fe9..00000000 --- a/implementation/core/src/main/java/io/smallrye/faulttolerance/core/async/types/AsyncTypesLogger.java +++ /dev/null @@ -1,10 +0,0 @@ -package io.smallrye.faulttolerance.core.async.types; - -import org.jboss.logging.BasicLogger; -import org.jboss.logging.Logger; -import org.jboss.logging.annotations.MessageLogger; - -@MessageLogger(projectCode = "SRFTL", length = 5) -interface AsyncTypesLogger extends BasicLogger { - AsyncTypesLogger LOG = Logger.getMessageLogger(AsyncTypesLogger.class, AsyncTypesLogger.class.getPackage().getName()); -} diff --git a/implementation/core/src/main/java/io/smallrye/faulttolerance/core/async/types/CompletionStageConverter.java b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/async/types/CompletionStageConverter.java deleted file mode 100644 index 09c89dff..00000000 --- a/implementation/core/src/main/java/io/smallrye/faulttolerance/core/async/types/CompletionStageConverter.java +++ /dev/null @@ -1,21 +0,0 @@ -package io.smallrye.faulttolerance.core.async.types; - -import java.util.concurrent.CompletionStage; -import java.util.function.Supplier; - -public class CompletionStageConverter implements AsyncTypeConverter> { - @Override - public Class type() { - return CompletionStage.class; - } - - @Override - public CompletionStage fromCompletionStage(Supplier> completionStageSupplier) { - return completionStageSupplier.get(); - } - - @Override - public CompletionStage toCompletionStage(CompletionStage asyncValue) { - return asyncValue; - } -} diff --git a/implementation/core/src/main/java/io/smallrye/faulttolerance/core/invocation/AsyncSupport.java b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/invocation/AsyncSupport.java new file mode 100644 index 00000000..095ad610 --- /dev/null +++ b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/invocation/AsyncSupport.java @@ -0,0 +1,21 @@ +package io.smallrye.faulttolerance.core.invocation; + +import java.util.concurrent.CompletionStage; + +// V = value type, e.g. String +// AT = async type that eventually produces V, e.g. CompletionStage or Uni +public interface AsyncSupport { + String description(); + + boolean applies(Class[] parameterTypes, Class returnType); + + CompletionStage toCompletionStage(Invoker invoker) throws Exception; + + AT fromCompletionStage(Invoker> invoker) throws Exception; + + // --- + + // only used for converting the return value of `FallbackHandler.handle()`, + // do not use elsewhere! + CompletionStage fallbackResultToCompletionStage(AT asyncValue); +} diff --git a/implementation/core/src/main/java/io/smallrye/faulttolerance/core/invocation/AsyncSupportRegistry.java b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/invocation/AsyncSupportRegistry.java new file mode 100644 index 00000000..41f6f28d --- /dev/null +++ b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/invocation/AsyncSupportRegistry.java @@ -0,0 +1,50 @@ +package io.smallrye.faulttolerance.core.invocation; + +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.ServiceLoader; + +public class AsyncSupportRegistry { + private static final List> registry; + + static { + List> list = new ArrayList<>(); + Iterable instances = System.getSecurityManager() != null + ? AccessController.doPrivileged((PrivilegedAction>) AsyncSupportRegistry::load) + : load(); + for (AsyncSupport instance : instances) { + list.add(instance); + } + registry = Collections.unmodifiableList(list); + } + + private static ServiceLoader load() { + return ServiceLoader.load(AsyncSupport.class, AsyncSupport.class.getClassLoader()); + } + + public static boolean isKnown(Class[] parameterTypes, Class returnType) { + for (AsyncSupport asyncSupport : registry) { + if (asyncSupport.applies(parameterTypes, returnType)) { + return true; + } + } + return false; + } + + public static AsyncSupport get(Class[] parameterTypes, Class returnType) { + for (AsyncSupport asyncSupport : registry) { + if (asyncSupport.applies(parameterTypes, returnType)) { + return (AsyncSupport) asyncSupport; + } + } + return null; + } + + public static Collection> allKnown() { + return registry; + } +} diff --git a/implementation/core/src/main/java/io/smallrye/faulttolerance/core/invocation/CompletionStageSupport.java b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/invocation/CompletionStageSupport.java new file mode 100644 index 00000000..73b9e47b --- /dev/null +++ b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/invocation/CompletionStageSupport.java @@ -0,0 +1,38 @@ +package io.smallrye.faulttolerance.core.invocation; + +import static io.smallrye.faulttolerance.core.util.CompletionStages.failedStage; + +import java.util.concurrent.CompletionStage; + +public class CompletionStageSupport implements AsyncSupport> { + @Override + public String description() { + return "return " + CompletionStage.class.getSimpleName(); + } + + @Override + public boolean applies(Class[] parameterTypes, Class returnType) { + return CompletionStage.class.equals(returnType); + } + + @Override + public CompletionStage toCompletionStage(Invoker> invoker) throws Exception { + return invoker.proceed(); + } + + @Override + public CompletionStage fromCompletionStage(Invoker> invoker) { + try { + return invoker.proceed(); + } catch (Exception e) { + return failedStage(e); + } + } + + // --- + + @Override + public CompletionStage fallbackResultToCompletionStage(CompletionStage completionStage) { + return completionStage; + } +} diff --git a/implementation/core/src/main/java/io/smallrye/faulttolerance/core/invocation/Invoker.java b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/invocation/Invoker.java new file mode 100644 index 00000000..8de11448 --- /dev/null +++ b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/invocation/Invoker.java @@ -0,0 +1,14 @@ +package io.smallrye.faulttolerance.core.invocation; + +import java.util.function.Function; + +public interface Invoker { + int parametersCount(); + + T getArgument(int index, Class parameterType); + + // callers should think about restoring the original argument later, if necessary + T replaceArgument(int index, Class parameterType, Function transformation); + + V proceed() throws Exception; +} diff --git a/implementation/core/src/main/java/io/smallrye/faulttolerance/core/invocation/NormalMethodInvoker.java b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/invocation/NormalMethodInvoker.java new file mode 100644 index 00000000..02279e43 --- /dev/null +++ b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/invocation/NormalMethodInvoker.java @@ -0,0 +1,50 @@ +package io.smallrye.faulttolerance.core.invocation; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.function.Function; + +import io.smallrye.faulttolerance.core.util.Preconditions; + +public class NormalMethodInvoker implements Invoker { + static final Object[] EMPTY_ARRAY = new Object[0]; + + private final Method method; + private final Object target; + private final Object[] arguments; + + public NormalMethodInvoker(Method method, Object target, Object[] arguments) { + if (arguments == null) { + arguments = EMPTY_ARRAY; + } + Preconditions.check(arguments.length, arguments.length == method.getParameterCount(), + "Argument array length must be " + method.getParameterCount()); + this.method = method; + this.target = target; + this.arguments = arguments; + } + + @Override + public int parametersCount() { + return method.getParameterCount(); + } + + @Override + public T getArgument(int index, Class parameterType) { + return parameterType.cast(arguments[index]); + } + + @Override + public T replaceArgument(int index, Class parameterType, Function transformation) { + T oldArg = parameterType.cast(arguments[index]); + T newArg = transformation.apply(oldArg); + arguments[index] = newArg; + return oldArg; + } + + @Override + @SuppressWarnings("unchecked") + public V proceed() throws InvocationTargetException, IllegalAccessException { + return (V) method.invoke(target, arguments); + } +} diff --git a/implementation/core/src/main/java/io/smallrye/faulttolerance/core/invocation/SpecialMethodInvoker.java b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/invocation/SpecialMethodInvoker.java new file mode 100644 index 00000000..a3f74ddc --- /dev/null +++ b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/invocation/SpecialMethodInvoker.java @@ -0,0 +1,64 @@ +package io.smallrye.faulttolerance.core.invocation; + +import static io.smallrye.faulttolerance.core.util.SneakyThrow.sneakyThrow; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; +import java.util.function.Function; + +import io.smallrye.faulttolerance.core.util.Preconditions; + +public class SpecialMethodInvoker implements Invoker { + private final MethodHandle methodHandle; + private final Object target; + private final Object[] arguments; + + public SpecialMethodInvoker(Method method, Object target, Object[] arguments) throws ReflectiveOperationException { + if (arguments == null) { + arguments = NormalMethodInvoker.EMPTY_ARRAY; + } + Preconditions.check(arguments.length, arguments.length == method.getParameterCount(), + "Argument array length must be " + method.getParameterCount()); + + Class declaringClazz = method.getDeclaringClass(); + Constructor constructor = MethodHandles.Lookup.class.getDeclaredConstructor(Class.class); + constructor.setAccessible(true); + this.methodHandle = constructor.newInstance(declaringClazz) + .in(declaringClazz) + .unreflectSpecial(method, declaringClazz); + this.target = target; + this.arguments = arguments; + } + + @Override + public int parametersCount() { + return arguments.length; + } + + @Override + public T getArgument(int index, Class parameterType) { + return parameterType.cast(arguments[index]); + } + + @Override + public T replaceArgument(int index, Class parameterType, Function transformation) { + T oldArg = parameterType.cast(arguments[index]); + T newArg = transformation.apply(oldArg); + arguments[index] = newArg; + return oldArg; + } + + @Override + @SuppressWarnings("unchecked") + public V proceed() { + try { + return (V) methodHandle + .bindTo(target) + .invokeWithArguments(arguments); + } catch (Throwable e) { + throw sneakyThrow(e); + } + } +} diff --git a/implementation/core/src/main/java/io/smallrye/faulttolerance/core/invocation/StrategyInvoker.java b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/invocation/StrategyInvoker.java new file mode 100644 index 00000000..b723f983 --- /dev/null +++ b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/invocation/StrategyInvoker.java @@ -0,0 +1,44 @@ +package io.smallrye.faulttolerance.core.invocation; + +import java.util.function.Function; + +import io.smallrye.faulttolerance.core.FaultToleranceStrategy; +import io.smallrye.faulttolerance.core.InvocationContext; + +public class StrategyInvoker implements Invoker { + private final Object[] arguments; // read-only! + private final FaultToleranceStrategy strategy; + private final InvocationContext context; + + public StrategyInvoker(Object[] arguments, FaultToleranceStrategy strategy, InvocationContext context) { + this.arguments = arguments; + this.strategy = strategy; + this.context = context; + } + + @Override + public int parametersCount() { + if (arguments == null) { + throw new UnsupportedOperationException(); + } + return arguments.length; + } + + @Override + public T getArgument(int index, Class parameterType) { + if (arguments == null) { + throw new UnsupportedOperationException(); + } + return parameterType.cast(arguments[index]); + } + + @Override + public T replaceArgument(int index, Class parameterType, Function transformation) { + throw new UnsupportedOperationException(); + } + + @Override + public V proceed() throws Exception { + return strategy.apply(context); + } +} diff --git a/implementation/core/src/main/java/io/smallrye/faulttolerance/core/util/Initializer.java b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/util/Initializer.java index d0c409d0..6bcebb08 100644 --- a/implementation/core/src/main/java/io/smallrye/faulttolerance/core/util/Initializer.java +++ b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/util/Initializer.java @@ -6,6 +6,7 @@ /** * Contains a sequence of {@link Runnable} actions and makes sure that they are only executed once. + * Concurrent threads are guaranteed to not exit {@code runOne()} before initialization has finished. */ public final class Initializer { private final Runnable[] actions; @@ -21,9 +22,15 @@ public Initializer(List actions) { } public void runOnce() { - if (ran.compareAndSet(false, true)) { - for (Runnable action : actions) { - action.run(); + if (ran.get()) { + return; + } + + synchronized (this) { + if (ran.compareAndSet(false, true)) { + for (Runnable action : actions) { + action.run(); + } } } } diff --git a/implementation/core/src/main/java9/io/smallrye/faulttolerance/core/invocation/SpecialMethodInvoker.java b/implementation/core/src/main/java9/io/smallrye/faulttolerance/core/invocation/SpecialMethodInvoker.java new file mode 100644 index 00000000..d2809471 --- /dev/null +++ b/implementation/core/src/main/java9/io/smallrye/faulttolerance/core/invocation/SpecialMethodInvoker.java @@ -0,0 +1,63 @@ +package io.smallrye.faulttolerance.core.invocation; + +import static io.smallrye.faulttolerance.core.util.SneakyThrow.sneakyThrow; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; +import java.util.function.Function; + +import io.smallrye.faulttolerance.core.util.Preconditions; + +public class SpecialMethodInvoker implements Invoker { + private final MethodHandle methodHandle; + private final Object target; + private final Object[] arguments; + + public SpecialMethodInvoker(Method method, Object target, Object[] arguments) throws ReflectiveOperationException { + if (arguments == null) { + arguments = NormalMethodInvoker.EMPTY_ARRAY; + } + Preconditions.check(arguments.length, arguments.length == method.getParameterCount(), + "Argument array length must be " + method.getParameterCount()); + + MethodType methodType = MethodType.methodType(method.getReturnType(), method.getParameterTypes()); + Class declaringClass = method.getDeclaringClass(); + this.methodHandle = MethodHandles.lookup() + .findSpecial(declaringClass, method.getName(), methodType, declaringClass); + this.target = target; + this.arguments = arguments; + } + + @Override + public int parametersCount() { + return arguments.length; + } + + @Override + public T getArgument(int index, Class parameterType) { + return parameterType.cast(arguments[index]); + } + + @Override + public T replaceArgument(int index, Class parameterType, Function transformation) { + T oldArg = parameterType.cast(arguments[index]); + T newArg = transformation.apply(oldArg); + arguments[index] = newArg; + return oldArg; + } + + @Override + @SuppressWarnings("unchecked") + public V proceed() { + try { + return (V) methodHandle + .bindTo(target) + .invokeWithArguments(arguments); + } catch (Throwable e) { + throw sneakyThrow(e); + } + } +} diff --git a/implementation/core/src/main/resources/META-INF/services/io.smallrye.faulttolerance.core.async.types.AsyncTypeConverter b/implementation/core/src/main/resources/META-INF/services/io.smallrye.faulttolerance.core.async.types.AsyncTypeConverter deleted file mode 100644 index 3502be72..00000000 --- a/implementation/core/src/main/resources/META-INF/services/io.smallrye.faulttolerance.core.async.types.AsyncTypeConverter +++ /dev/null @@ -1 +0,0 @@ -io.smallrye.faulttolerance.core.async.types.CompletionStageConverter \ No newline at end of file diff --git a/implementation/core/src/main/resources/META-INF/services/io.smallrye.faulttolerance.core.invocation.AsyncSupport b/implementation/core/src/main/resources/META-INF/services/io.smallrye.faulttolerance.core.invocation.AsyncSupport new file mode 100644 index 00000000..4964f77f --- /dev/null +++ b/implementation/core/src/main/resources/META-INF/services/io.smallrye.faulttolerance.core.invocation.AsyncSupport @@ -0,0 +1 @@ +io.smallrye.faulttolerance.core.invocation.CompletionStageSupport \ No newline at end of file diff --git a/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/DefaultMethodFallbackProvider.java b/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/DefaultMethodFallbackProvider.java deleted file mode 100644 index eb2e2ae5..00000000 --- a/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/DefaultMethodFallbackProvider.java +++ /dev/null @@ -1,29 +0,0 @@ -package io.smallrye.faulttolerance; - -import java.lang.invoke.MethodHandles.Lookup; -import java.lang.reflect.Constructor; -import java.lang.reflect.Method; - -/** - * Workaround for default fallback methods (used e.g. in MP Rest Client). - * - * @author Martin Kouba - */ -class DefaultMethodFallbackProvider { - - static Object getFallback(Method fallbackMethod, ExecutionContextWithInvocationContext ctx) - throws Throwable { - // This should work in Java 8 - Class declaringClazz = fallbackMethod.getDeclaringClass(); - Constructor constructor = Lookup.class.getDeclaredConstructor(Class.class); - constructor.setAccessible(true); - return constructor.newInstance(declaringClazz) - .in(declaringClazz) - .unreflectSpecial(fallbackMethod, declaringClazz) - .bindTo(ctx.getTarget()) - .invokeWithArguments(ctx.getParameters()); - } - - private DefaultMethodFallbackProvider() { - } -} 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 439ca6a9..6e85dcca 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 @@ -17,7 +17,6 @@ package io.smallrye.faulttolerance; import static io.smallrye.faulttolerance.core.Invocation.invocation; -import static io.smallrye.faulttolerance.core.util.CompletionStages.failedStage; import static io.smallrye.faulttolerance.core.util.SneakyThrow.sneakyThrow; import java.lang.reflect.InvocationTargetException; @@ -27,6 +26,7 @@ import java.time.temporal.ChronoUnit; import java.util.Arrays; import java.util.UUID; +import java.util.concurrent.Callable; import java.util.concurrent.CompletionStage; import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; @@ -51,8 +51,6 @@ import io.smallrye.faulttolerance.core.async.CompletionStageExecution; import io.smallrye.faulttolerance.core.async.FutureExecution; import io.smallrye.faulttolerance.core.async.RememberEventLoop; -import io.smallrye.faulttolerance.core.async.types.AsyncTypes; -import io.smallrye.faulttolerance.core.async.types.AsyncTypesConversion; import io.smallrye.faulttolerance.core.bulkhead.CompletionStageThreadPoolBulkhead; import io.smallrye.faulttolerance.core.bulkhead.FutureThreadPoolBulkhead; import io.smallrye.faulttolerance.core.bulkhead.SemaphoreBulkhead; @@ -64,6 +62,12 @@ import io.smallrye.faulttolerance.core.fallback.CompletionStageFallback; import io.smallrye.faulttolerance.core.fallback.Fallback; import io.smallrye.faulttolerance.core.fallback.FallbackFunction; +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.NormalMethodInvoker; +import io.smallrye.faulttolerance.core.invocation.SpecialMethodInvoker; +import io.smallrye.faulttolerance.core.invocation.StrategyInvoker; import io.smallrye.faulttolerance.core.metrics.CompletionStageMetricsCollector; import io.smallrye.faulttolerance.core.metrics.MetricsCollector; import io.smallrye.faulttolerance.core.metrics.MetricsRecorder; @@ -88,6 +92,7 @@ import io.smallrye.faulttolerance.core.util.ExceptionDecision; import io.smallrye.faulttolerance.core.util.SetBasedExceptionDecision; import io.smallrye.faulttolerance.core.util.SetOfThrowables; +import io.smallrye.faulttolerance.internal.InterceptionInvoker; import io.smallrye.faulttolerance.internal.InterceptionPoint; import io.smallrye.faulttolerance.internal.RequestScopeActivator; import io.smallrye.faulttolerance.internal.StrategyCache; @@ -152,7 +157,7 @@ public FaultToleranceInterceptor( } @AroundInvoke - public Object interceptCommand(InvocationContext interceptionContext) throws Exception { + public Object intercept(InvocationContext interceptionContext) throws Exception { Method method = interceptionContext.getMethod(); Class beanClass = interceptedBean != null ? interceptedBean.getBeanClass() : method.getDeclaringClass(); InterceptionPoint point = new InterceptionPoint(beanClass, interceptionContext); @@ -168,16 +173,27 @@ public Object interceptCommand(InvocationContext interceptionContext) throws Exc } } - private Object asyncFlow(FaultToleranceOperation operation, InvocationContext interceptionContext, + private AT asyncFlow(FaultToleranceOperation operation, InvocationContext interceptionContext, InterceptionPoint point) { - FaultToleranceStrategy strategy = cache.getStrategy(point, () -> prepareAsyncStrategy(operation, point)); + AsyncSupport asyncSupport = AsyncSupportRegistry.get(operation.getParameterTypes(), operation.getReturnType()); + if (asyncSupport == null) { + throw new FaultToleranceException("Unknown async invocation: " + operation); + } + + FaultToleranceStrategy> strategy = cache.getStrategy(point, + () -> prepareAsyncStrategy(operation, point)); + + Invoker invoker = new InterceptionInvoker<>(interceptionContext); - io.smallrye.faulttolerance.core.InvocationContext ctx = invocationContext(operation, interceptionContext); + io.smallrye.faulttolerance.core.InvocationContext> ctx = invocationContext( + () -> asyncSupport.toCompletionStage(invoker), interceptionContext, operation); + Invoker> wrapper = new StrategyInvoker<>(interceptionContext.getParameters(), + strategy, ctx); try { - return strategy.apply(ctx); + return asyncSupport.fromCompletionStage(wrapper); } catch (Exception e) { - return AsyncTypes.get(operation.getReturnType()).fromCompletionStage(() -> failedStage(e)); + throw sneakyThrow(e); } } @@ -185,7 +201,8 @@ private T syncFlow(FaultToleranceOperation operation, InvocationContext inte throws Exception { FaultToleranceStrategy strategy = cache.getStrategy(point, () -> prepareSyncStrategy(operation, point)); - io.smallrye.faulttolerance.core.InvocationContext ctx = invocationContext(operation, interceptionContext); + io.smallrye.faulttolerance.core.InvocationContext ctx = invocationContext( + () -> (T) interceptionContext.proceed(), interceptionContext, operation); return strategy.apply(ctx); } @@ -194,16 +211,17 @@ private Future futureFlow(FaultToleranceOperation operation, InvocationCo InterceptionPoint point) throws Exception { FaultToleranceStrategy> strategy = cache.getStrategy(point, () -> prepareFutureStrategy(operation, point)); - io.smallrye.faulttolerance.core.InvocationContext> ctx = invocationContext(operation, interceptionContext); + io.smallrye.faulttolerance.core.InvocationContext> ctx = invocationContext( + () -> (Future) interceptionContext.proceed(), interceptionContext, operation); return strategy.apply(ctx); } - @SuppressWarnings("unchecked") - private io.smallrye.faulttolerance.core.InvocationContext invocationContext(FaultToleranceOperation operation, - InvocationContext interceptionContext) { + private io.smallrye.faulttolerance.core.InvocationContext invocationContext(Callable callable, + InvocationContext interceptionContext, FaultToleranceOperation operation) { + io.smallrye.faulttolerance.core.InvocationContext result = new io.smallrye.faulttolerance.core.InvocationContext<>( - () -> (T) interceptionContext.proceed()); + callable); result.set(InvocationContext.class, interceptionContext); @@ -215,27 +233,9 @@ private io.smallrye.faulttolerance.core.InvocationContext invocationConte return result; } - private FaultToleranceStrategy prepareAsyncStrategy(FaultToleranceOperation operation, InterceptionPoint point) { - // FaultToleranceStrategy expects that the input type and output type are the same, which - // isn't true for the conversion strategies used below (even though the entire chain does - // retain the type, the conversions are only intermediate) - // that's why we use raw types here, to work around this design choice - - FaultToleranceStrategy result = invocation(); - - result = new AsyncTypesConversion.ToCompletionStage(result, AsyncTypes.get(operation.getReturnType())); - - result = prepareComplectionStageChain((FaultToleranceStrategy>) result, operation, point); - - result = new AsyncTypesConversion.FromCompletionStage(result, AsyncTypes.get(operation.getReturnType())); - - return result; - } - - private FaultToleranceStrategy> prepareComplectionStageChain( - FaultToleranceStrategy> invocation, FaultToleranceOperation operation, InterceptionPoint point) { - - FaultToleranceStrategy> result = invocation; + private FaultToleranceStrategy> prepareAsyncStrategy(FaultToleranceOperation operation, + InterceptionPoint point) { + FaultToleranceStrategy> result = invocation(); result = new RequestScopeActivator<>(result, requestContextController); @@ -470,7 +470,7 @@ private FallbackFunction prepareFallbackFunction(InterceptionPoint point, } } - Class returnType = operation.getReturnType(); + AsyncSupport asyncSupport = AsyncSupportRegistry.get(operation.getParameterTypes(), operation.getReturnType()); FallbackFunction fallbackFunction; @@ -479,21 +479,13 @@ private FallbackFunction prepareFallbackFunction(InterceptionPoint point, boolean isDefault = fallbackMethodFinal.isDefault(); fallbackFunction = ctx -> { InvocationContext interceptionContext = ctx.invocationContext.get(InvocationContext.class); - ExecutionContextWithInvocationContext executionContext = new ExecutionContextWithInvocationContext( - interceptionContext); try { - V result; - if (isDefault) { - // Workaround for default methods (used e.g. in MP Rest Client) - //noinspection unchecked - result = (V) DefaultMethodFallbackProvider.getFallback(fallbackMethodFinal, executionContext); - } else { - //noinspection unchecked - result = (V) fallbackMethodFinal.invoke(interceptionContext.getTarget(), - interceptionContext.getParameters()); - } - result = AsyncTypes.toCompletionStageIfRequired(result, returnType); - return result; + Invoker invoker = isDefault + ? new SpecialMethodInvoker(fallbackMethodFinal, interceptionContext.getTarget(), + interceptionContext.getParameters()) + : new NormalMethodInvoker(fallbackMethodFinal, interceptionContext.getTarget(), + interceptionContext.getParameters()); + return asyncSupport == null ? (V) invoker.proceed() : (V) asyncSupport.toCompletionStage(invoker); } catch (Throwable e) { if (e instanceof InvocationTargetException) { e = e.getCause(); @@ -513,7 +505,9 @@ private FallbackFunction prepareFallbackFunction(InterceptionPoint point, interceptionContext); executionContext.setFailure(ctx.failure); V result = fallbackHandler.handle(executionContext); - result = AsyncTypes.toCompletionStageIfRequired(result, returnType); + if (asyncSupport != null) { + result = (V) asyncSupport.fallbackResultToCompletionStage(result); + } return result; }; } else { diff --git a/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/SpecCompatibility.java b/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/SpecCompatibility.java index 91a9bd6a..c45b39fe 100644 --- a/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/SpecCompatibility.java +++ b/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/SpecCompatibility.java @@ -8,7 +8,7 @@ import org.eclipse.microprofile.config.inject.ConfigProperty; import io.smallrye.faulttolerance.config.FaultToleranceOperation; -import io.smallrye.faulttolerance.core.async.types.AsyncTypes; +import io.smallrye.faulttolerance.core.invocation.AsyncSupportRegistry; @Singleton public class SpecCompatibility { @@ -21,13 +21,14 @@ public SpecCompatibility( } public boolean isOperationTrulyAsynchronous(FaultToleranceOperation operation) { - boolean returnTypeMatches = AsyncTypes.isKnown(operation.getReturnType()); + //boolean supported = AsyncTypes.isKnown(operation.getReturnType()); + boolean supported = AsyncSupportRegistry.isKnown(operation.getParameterTypes(), operation.getReturnType()); if (compatible) { boolean hasAnnotation = operation.hasAsynchronous() || operation.hasBlocking() || operation.hasNonBlocking(); - return returnTypeMatches && hasAnnotation; + return supported && hasAnnotation; } else { - return returnTypeMatches; + return supported; } } diff --git a/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/config/AsyncValidation.java b/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/config/AsyncValidation.java deleted file mode 100644 index 588f6939..00000000 --- a/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/config/AsyncValidation.java +++ /dev/null @@ -1,21 +0,0 @@ -package io.smallrye.faulttolerance.config; - -import java.util.StringJoiner; -import java.util.concurrent.Future; - -import io.smallrye.faulttolerance.core.async.types.AsyncTypeConverter; -import io.smallrye.faulttolerance.core.async.types.AsyncTypes; - -final class AsyncValidation { - static boolean isAcceptableReturnType(Class returnType) { - return Future.class.equals(returnType) || AsyncTypes.isKnown(returnType); - } - - static String describeKnownAsyncTypes() { - StringJoiner result = new StringJoiner(" or "); - for (AsyncTypeConverter converter : AsyncTypes.allKnown()) { - result.add(converter.type().getName()); - } - return result.toString(); - } -} diff --git a/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/config/AsynchronousConfig.java b/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/config/AsynchronousConfig.java index ffe017de..321e7243 100644 --- a/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/config/AsynchronousConfig.java +++ b/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/config/AsynchronousConfig.java @@ -1,18 +1,31 @@ package io.smallrye.faulttolerance.config; +import java.util.StringJoiner; +import java.util.concurrent.Future; + import org.eclipse.microprofile.faulttolerance.Asynchronous; import org.eclipse.microprofile.faulttolerance.exceptions.FaultToleranceDefinitionException; import io.smallrye.faulttolerance.autoconfig.AutoConfig; import io.smallrye.faulttolerance.autoconfig.Config; +import io.smallrye.faulttolerance.core.invocation.AsyncSupport; +import io.smallrye.faulttolerance.core.invocation.AsyncSupportRegistry; @AutoConfig public interface AsynchronousConfig extends Asynchronous, Config { @Override default void validate() { - if (!AsyncValidation.isAcceptableReturnType(method().returnType)) { - throw new FaultToleranceDefinitionException("Invalid @Asynchronous on " + method() - + ": must return java.util.concurrent.Future or " + AsyncValidation.describeKnownAsyncTypes()); + Class[] parameterTypes = method().parameterTypes; + Class returnType = method().returnType; + if (Future.class.equals(returnType) || AsyncSupportRegistry.isKnown(parameterTypes, returnType)) { + return; + } + + StringJoiner knownAsync = new StringJoiner(" or "); + for (AsyncSupport asyncSupport : AsyncSupportRegistry.allKnown()) { + knownAsync.add(asyncSupport.description()); } + throw new FaultToleranceDefinitionException("Invalid @Asynchronous on " + method() + + ": must return java.util.concurrent.Future or " + knownAsync); } } diff --git a/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/config/FallbackConfig.java b/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/config/FallbackConfig.java index 6910be1c..dd92500e 100644 --- a/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/config/FallbackConfig.java +++ b/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/config/FallbackConfig.java @@ -64,6 +64,11 @@ default void validate() { } } Type boxedReturnType = FallbackValidation.box(guardedMethod.getGenericReturnType()); + + if (KotlinSupport.isSuspendingFunction(guardedMethod)) { + boxedReturnType = KotlinSupport.getSuspendingFunctionResultType(guardedMethod); + } + if (!boxedReturnType.equals(fallbackType)) { throw new FaultToleranceDefinitionException(INVALID_FALLBACK_ON + method() + ": fallback handler's type " + fallbackType + " is not the same as method's return type"); 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 92bd9c62..7ead4658 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 @@ -120,6 +120,10 @@ private FaultToleranceOperation(Class beanClass, this.customBackoff = customBackoff; } + public Class[] getParameterTypes() { + return methodDescriptor.parameterTypes; + } + public Class getReturnType() { return methodDescriptor.returnType; } diff --git a/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/config/KotlinSupport.java b/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/config/KotlinSupport.java new file mode 100644 index 00000000..573c5614 --- /dev/null +++ b/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/config/KotlinSupport.java @@ -0,0 +1,34 @@ +package io.smallrye.faulttolerance.config; + +import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.lang.reflect.WildcardType; + +// TODO this would ideally live in the `kotlin` module +final class KotlinSupport { + static boolean isSuspendingFunction(Method method) { + int params = method.getParameterCount(); + return params > 0 && method.getParameterTypes()[params - 1].getName().equals("kotlin.coroutines.Continuation"); + } + + static Type getSuspendingFunctionResultType(Method method) { + if (!isSuspendingFunction(method)) { + throw new IllegalArgumentException("Not a suspend function: " + method); + } + + Type lastParameter = method.getGenericParameterTypes()[method.getParameterCount() - 1]; + if (!(lastParameter instanceof ParameterizedType)) { + throw new IllegalArgumentException("Continuation parameter type not parameterized: " + lastParameter); + } + Type resultType = ((ParameterizedType) lastParameter).getActualTypeArguments()[0]; + if (!(resultType instanceof WildcardType)) { + throw new IllegalArgumentException("Continuation parameter type argument not wildcard: " + resultType); + } + Type[] lowerBounds = ((WildcardType) resultType).getLowerBounds(); + if (lowerBounds.length == 0) { + throw new IllegalArgumentException("Continuation parameter type argument without lower bound: " + resultType); + } + return lowerBounds[0]; + } +} diff --git a/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/internal/InterceptionInvoker.java b/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/internal/InterceptionInvoker.java new file mode 100644 index 00000000..8f7b7e08 --- /dev/null +++ b/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/internal/InterceptionInvoker.java @@ -0,0 +1,40 @@ +package io.smallrye.faulttolerance.internal; + +import java.util.function.Function; + +import javax.interceptor.InvocationContext; + +import io.smallrye.faulttolerance.core.invocation.Invoker; + +public class InterceptionInvoker implements Invoker { + private final InvocationContext interceptionContext; + + public InterceptionInvoker(InvocationContext interceptionContext) { + this.interceptionContext = interceptionContext; + } + + @Override + public int parametersCount() { + return interceptionContext.getParameters().length; + } + + @Override + public T getArgument(int index, Class parameterType) { + return parameterType.cast(interceptionContext.getParameters()[index]); + } + + @Override + public T replaceArgument(int index, Class parameterType, Function transformation) { + Object[] arguments = interceptionContext.getParameters(); + T oldArg = parameterType.cast(arguments[index]); + T newArg = transformation.apply(oldArg); + arguments[index] = newArg; + interceptionContext.setParameters(arguments); + return oldArg; + } + + @Override + public V proceed() throws Exception { + return (V) interceptionContext.proceed(); + } +} diff --git a/implementation/fault-tolerance/src/main/java9/io/smallrye/faulttolerance/DefaultMethodFallbackProvider.java b/implementation/fault-tolerance/src/main/java9/io/smallrye/faulttolerance/DefaultMethodFallbackProvider.java deleted file mode 100644 index d87054af..00000000 --- a/implementation/fault-tolerance/src/main/java9/io/smallrye/faulttolerance/DefaultMethodFallbackProvider.java +++ /dev/null @@ -1,27 +0,0 @@ -package io.smallrye.faulttolerance; - -import java.lang.invoke.MethodHandles; -import java.lang.invoke.MethodType; -import java.lang.invoke.MethodHandles.Lookup; -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; - -/** - * Workaround for default fallback methods (used e.g. in MP Rest Client). - * - * @author Martin Kouba - */ -class DefaultMethodFallbackProvider { - - static Object getFallback(Method fallbackMethod, ExecutionContextWithInvocationContext ctx) - throws IllegalAccessException, InstantiationException, IllegalArgumentException, InvocationTargetException, Throwable { - // This should work in Java 9+ - Class declaringClazz = fallbackMethod.getDeclaringClass(); - return MethodHandles.lookup() - .findSpecial(declaringClazz, fallbackMethod.getName(), - MethodType.methodType(fallbackMethod.getReturnType(), fallbackMethod.getParameterTypes()), declaringClazz) - .bindTo(ctx.getTarget()).invokeWithArguments(ctx.getParameters()); - } - -} diff --git a/implementation/kotlin/pom.xml b/implementation/kotlin/pom.xml new file mode 100644 index 00000000..f1d49610 --- /dev/null +++ b/implementation/kotlin/pom.xml @@ -0,0 +1,106 @@ + + + 4.0.0 + + + io.smallrye + smallrye-fault-tolerance-implementation-parent + 5.3.3-SNAPSHOT + + + smallrye-fault-tolerance-kotlin + + SmallRye Fault Tolerance: Kotlin Integration + + + + io.smallrye + smallrye-fault-tolerance-api + + + jakarta.enterprise + jakarta.enterprise.cdi-api + + + org.eclipse.microprofile.fault-tolerance + microprofile-fault-tolerance-api + + + + + io.smallrye + smallrye-fault-tolerance-core + + + + org.jetbrains.kotlin + kotlin-stdlib + + + + + + + org.jetbrains.kotlin + kotlin-maven-plugin + ${version.kotlin} + + + compile + + compile + + + + ${project.basedir}/src/main/kotlin + + + + + test-compile + + test-compile + + + + ${project.basedir}/src/test/kotlin + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + + default-compile + none + + + + default-testCompile + none + + + java-compile + compile + + compile + + + + java-test-compile + test-compile + + testCompile + + + + + + + + diff --git a/implementation/kotlin/src/main/kotlin/io/smallrye/faulttolerance/kotlin/impl/CoroutineSupport.kt b/implementation/kotlin/src/main/kotlin/io/smallrye/faulttolerance/kotlin/impl/CoroutineSupport.kt new file mode 100644 index 00000000..e6dddb1e --- /dev/null +++ b/implementation/kotlin/src/main/kotlin/io/smallrye/faulttolerance/kotlin/impl/CoroutineSupport.kt @@ -0,0 +1,95 @@ +package io.smallrye.faulttolerance.kotlin.impl + +import io.smallrye.faulttolerance.core.invocation.AsyncSupport +import io.smallrye.faulttolerance.core.invocation.Invoker +import io.smallrye.faulttolerance.core.util.CompletionStages.failedFuture +import org.eclipse.microprofile.faulttolerance.exceptions.FaultToleranceException +import java.util.concurrent.CompletableFuture +import java.util.concurrent.CompletableFuture.completedFuture +import java.util.concurrent.CompletionStage +import java.util.concurrent.ExecutionException +import kotlin.coroutines.Continuation +import kotlin.coroutines.CoroutineContext +import kotlin.coroutines.intrinsics.COROUTINE_SUSPENDED +import kotlin.coroutines.resume +import kotlin.coroutines.resumeWithException + +// the `Any?` type is used here similarly to Kotlin-transformed `suspend` functions: +// it stands for a non-denotable union type `T | COROUTINE_SUSPENDED` +class CoroutineSupport : AsyncSupport { + override fun description(): String { + return "be a Kotlin suspend function" + } + + override fun applies(parameterTypes: Array>, returnType: Class<*>): Boolean { + return parameterTypes.isNotEmpty() && parameterTypes.last() == Continuation::class.java + } + + override fun toCompletionStage(invoker: Invoker): CompletionStage { + val future = CompletableFuture() + + val index = invoker.parametersCount() - 1 + val previousContinuation = invoker.replaceArgument(index, Continuation::class.java) { originalContinuation -> + object : Continuation { + override val context: CoroutineContext + get() = originalContinuation.context + + override fun resumeWith(result: Result) { + result.fold( + onSuccess = { value -> future.complete(value) }, + onFailure = { exception -> future.completeExceptionally(exception) } + ) + } + } + } + + try { + val result = invoker.proceed() + if (result !== COROUTINE_SUSPENDED) { + return completedFuture(result as T) + } + } catch (e: Exception) { + return failedFuture(e) + } finally { + invoker.replaceArgument(index, Continuation::class.java) { previousContinuation } + } + + return future + } + + override fun fromCompletionStage(invoker: Invoker>): Any? { + // remember the continuation early, though there should be no harm in looking it up later + val index = invoker.parametersCount() - 1 + val continuation = invoker.getArgument(index, Continuation::class.java) as Continuation + + val completableFuture = invoker.proceed().toCompletableFuture() + + if (completableFuture.isDone) { + try { + return completableFuture.get() + } catch (e: ExecutionException) { + throw e.cause!! + } + } + + completableFuture.whenComplete { value, exception -> + if (exception == null) { + continuation.resume(value) + } else { + continuation.resumeWithException(exception) + } + } + return COROUTINE_SUSPENDED + } + + // --- + + override fun fallbackResultToCompletionStage(value: Any?): CompletionStage { + if (value === COROUTINE_SUSPENDED) { + // should never happen + throw FaultToleranceException("Unexpected $value") + } else { + return completedFuture(value as T) + } + } +} diff --git a/implementation/kotlin/src/main/resources/META-INF/services/io.smallrye.faulttolerance.core.invocation.AsyncSupport b/implementation/kotlin/src/main/resources/META-INF/services/io.smallrye.faulttolerance.core.invocation.AsyncSupport new file mode 100644 index 00000000..0d90b119 --- /dev/null +++ b/implementation/kotlin/src/main/resources/META-INF/services/io.smallrye.faulttolerance.core.invocation.AsyncSupport @@ -0,0 +1 @@ +io.smallrye.faulttolerance.kotlin.impl.CoroutineSupport \ No newline at end of file diff --git a/implementation/mutiny/src/main/java/io/smallrye/faulttolerance/mutiny/impl/UniConverter.java b/implementation/mutiny/src/main/java/io/smallrye/faulttolerance/mutiny/impl/UniConverter.java deleted file mode 100644 index 7043e92e..00000000 --- a/implementation/mutiny/src/main/java/io/smallrye/faulttolerance/mutiny/impl/UniConverter.java +++ /dev/null @@ -1,24 +0,0 @@ -package io.smallrye.faulttolerance.mutiny.impl; - -import java.util.concurrent.CompletionStage; -import java.util.function.Supplier; - -import io.smallrye.faulttolerance.core.async.types.AsyncTypeConverter; -import io.smallrye.mutiny.Uni; - -public class UniConverter implements AsyncTypeConverter> { - @Override - public Class type() { - return Uni.class; - } - - @Override - public Uni fromCompletionStage(Supplier> completionStageSupplier) { - return Uni.createFrom().completionStage(completionStageSupplier); - } - - @Override - public CompletionStage toCompletionStage(Uni uni) { - return uni.subscribeAsCompletionStage(); - } -} diff --git a/implementation/mutiny/src/main/java/io/smallrye/faulttolerance/mutiny/impl/UniSupport.java b/implementation/mutiny/src/main/java/io/smallrye/faulttolerance/mutiny/impl/UniSupport.java new file mode 100644 index 00000000..4b3c6c46 --- /dev/null +++ b/implementation/mutiny/src/main/java/io/smallrye/faulttolerance/mutiny/impl/UniSupport.java @@ -0,0 +1,44 @@ +package io.smallrye.faulttolerance.mutiny.impl; + +import static io.smallrye.faulttolerance.core.util.SneakyThrow.sneakyThrow; + +import java.util.concurrent.CompletionStage; + +import io.smallrye.faulttolerance.core.invocation.AsyncSupport; +import io.smallrye.faulttolerance.core.invocation.Invoker; +import io.smallrye.mutiny.Uni; + +public class UniSupport implements AsyncSupport> { + @Override + public String description() { + return "return " + Uni.class.getSimpleName(); + } + + @Override + public boolean applies(Class[] parameterTypes, Class returnType) { + return Uni.class.equals(returnType); + } + + @Override + public CompletionStage toCompletionStage(Invoker> invoker) throws Exception { + return invoker.proceed().subscribeAsCompletionStage(); + } + + @Override + public Uni fromCompletionStage(Invoker> invoker) { + return Uni.createFrom().completionStage(() -> { + try { + return invoker.proceed(); + } catch (Exception e) { + throw sneakyThrow(e); + } + }); + } + + // --- + + @Override + public CompletionStage fallbackResultToCompletionStage(Uni uni) { + return uni.subscribeAsCompletionStage(); + } +} diff --git a/implementation/mutiny/src/main/resources/META-INF/services/io.smallrye.faulttolerance.core.async.types.AsyncTypeConverter b/implementation/mutiny/src/main/resources/META-INF/services/io.smallrye.faulttolerance.core.async.types.AsyncTypeConverter deleted file mode 100644 index c0d8ec02..00000000 --- a/implementation/mutiny/src/main/resources/META-INF/services/io.smallrye.faulttolerance.core.async.types.AsyncTypeConverter +++ /dev/null @@ -1 +0,0 @@ -io.smallrye.faulttolerance.mutiny.impl.UniConverter \ No newline at end of file diff --git a/implementation/mutiny/src/main/resources/META-INF/services/io.smallrye.faulttolerance.core.invocation.AsyncSupport b/implementation/mutiny/src/main/resources/META-INF/services/io.smallrye.faulttolerance.core.invocation.AsyncSupport new file mode 100644 index 00000000..d4c53a1e --- /dev/null +++ b/implementation/mutiny/src/main/resources/META-INF/services/io.smallrye.faulttolerance.core.invocation.AsyncSupport @@ -0,0 +1 @@ +io.smallrye.faulttolerance.mutiny.impl.UniSupport \ No newline at end of file diff --git a/implementation/pom.xml b/implementation/pom.xml index 6f5369e1..00c31d78 100644 --- a/implementation/pom.xml +++ b/implementation/pom.xml @@ -30,6 +30,8 @@ core + + kotlin mutiny rxjava3 vertx diff --git a/implementation/rxjava3/src/main/java/io/smallrye/faulttolerance/rxjava3/impl/CompletableConverter.java b/implementation/rxjava3/src/main/java/io/smallrye/faulttolerance/rxjava3/impl/CompletableConverter.java deleted file mode 100644 index cf9938c1..00000000 --- a/implementation/rxjava3/src/main/java/io/smallrye/faulttolerance/rxjava3/impl/CompletableConverter.java +++ /dev/null @@ -1,24 +0,0 @@ -package io.smallrye.faulttolerance.rxjava3.impl; - -import java.util.concurrent.CompletionStage; -import java.util.function.Supplier; - -import io.reactivex.rxjava3.core.Completable; -import io.smallrye.faulttolerance.core.async.types.AsyncTypeConverter; - -public class CompletableConverter implements AsyncTypeConverter { - @Override - public Class type() { - return Completable.class; - } - - @Override - public Completable fromCompletionStage(Supplier> completionStageSupplier) { - return Completable.defer(() -> Completable.fromCompletionStage(completionStageSupplier.get())); - } - - @Override - public CompletionStage toCompletionStage(Completable completable) { - return completable.toCompletionStage(null); - } -} diff --git a/implementation/rxjava3/src/main/java/io/smallrye/faulttolerance/rxjava3/impl/CompletableSupport.java b/implementation/rxjava3/src/main/java/io/smallrye/faulttolerance/rxjava3/impl/CompletableSupport.java new file mode 100644 index 00000000..772d0b46 --- /dev/null +++ b/implementation/rxjava3/src/main/java/io/smallrye/faulttolerance/rxjava3/impl/CompletableSupport.java @@ -0,0 +1,34 @@ +package io.smallrye.faulttolerance.rxjava3.impl; + +import java.util.concurrent.CompletionStage; + +import io.reactivex.rxjava3.core.Completable; +import io.smallrye.faulttolerance.core.invocation.AsyncSupport; +import io.smallrye.faulttolerance.core.invocation.Invoker; + +public class CompletableSupport implements AsyncSupport { + @Override + public String description() { + return "return " + Completable.class.getSimpleName(); + } + + @Override + public boolean applies(Class[] parameterTypes, Class returnType) { + return Completable.class.equals(returnType); + } + + @Override + public CompletionStage toCompletionStage(Invoker invoker) throws Exception { + return invoker.proceed().toCompletionStage(null); + } + + @Override + public Completable fromCompletionStage(Invoker> invoker) { + return Completable.defer(() -> Completable.fromCompletionStage(invoker.proceed())); + } + + @Override + public CompletionStage fallbackResultToCompletionStage(Completable completable) { + return completable.toCompletionStage(null); + } +} diff --git a/implementation/rxjava3/src/main/java/io/smallrye/faulttolerance/rxjava3/impl/MaybeConverter.java b/implementation/rxjava3/src/main/java/io/smallrye/faulttolerance/rxjava3/impl/MaybeConverter.java deleted file mode 100644 index e5075b74..00000000 --- a/implementation/rxjava3/src/main/java/io/smallrye/faulttolerance/rxjava3/impl/MaybeConverter.java +++ /dev/null @@ -1,24 +0,0 @@ -package io.smallrye.faulttolerance.rxjava3.impl; - -import java.util.concurrent.CompletionStage; -import java.util.function.Supplier; - -import io.reactivex.rxjava3.core.Maybe; -import io.smallrye.faulttolerance.core.async.types.AsyncTypeConverter; - -public class MaybeConverter implements AsyncTypeConverter> { - @Override - public Class type() { - return Maybe.class; - } - - @Override - public Maybe fromCompletionStage(Supplier> completionStageSupplier) { - return Maybe.defer(() -> Maybe.fromCompletionStage(completionStageSupplier.get())); - } - - @Override - public CompletionStage toCompletionStage(Maybe maybe) { - return maybe.toCompletionStage(null); - } -} diff --git a/implementation/rxjava3/src/main/java/io/smallrye/faulttolerance/rxjava3/impl/MaybeSupport.java b/implementation/rxjava3/src/main/java/io/smallrye/faulttolerance/rxjava3/impl/MaybeSupport.java new file mode 100644 index 00000000..671d8a65 --- /dev/null +++ b/implementation/rxjava3/src/main/java/io/smallrye/faulttolerance/rxjava3/impl/MaybeSupport.java @@ -0,0 +1,34 @@ +package io.smallrye.faulttolerance.rxjava3.impl; + +import java.util.concurrent.CompletionStage; + +import io.reactivex.rxjava3.core.Maybe; +import io.smallrye.faulttolerance.core.invocation.AsyncSupport; +import io.smallrye.faulttolerance.core.invocation.Invoker; + +public class MaybeSupport implements AsyncSupport> { + @Override + public String description() { + return "return " + Maybe.class.getSimpleName(); + } + + @Override + public boolean applies(Class[] parameterTypes, Class returnType) { + return Maybe.class.equals(returnType); + } + + @Override + public CompletionStage toCompletionStage(Invoker> invoker) throws Exception { + return invoker.proceed().toCompletionStage(null); + } + + @Override + public Maybe fromCompletionStage(Invoker> invoker) { + return Maybe.defer(() -> Maybe.fromCompletionStage(invoker.proceed())); + } + + @Override + public CompletionStage fallbackResultToCompletionStage(Maybe maybe) { + return maybe.toCompletionStage(null); + } +} diff --git a/implementation/rxjava3/src/main/java/io/smallrye/faulttolerance/rxjava3/impl/SingleConverter.java b/implementation/rxjava3/src/main/java/io/smallrye/faulttolerance/rxjava3/impl/SingleConverter.java deleted file mode 100644 index 99b9ade1..00000000 --- a/implementation/rxjava3/src/main/java/io/smallrye/faulttolerance/rxjava3/impl/SingleConverter.java +++ /dev/null @@ -1,24 +0,0 @@ -package io.smallrye.faulttolerance.rxjava3.impl; - -import java.util.concurrent.CompletionStage; -import java.util.function.Supplier; - -import io.reactivex.rxjava3.core.Single; -import io.smallrye.faulttolerance.core.async.types.AsyncTypeConverter; - -public class SingleConverter implements AsyncTypeConverter> { - @Override - public Class type() { - return Single.class; - } - - @Override - public Single fromCompletionStage(Supplier> completionStageSupplier) { - return Single.defer(() -> Single.fromCompletionStage(completionStageSupplier.get())); - } - - @Override - public CompletionStage toCompletionStage(Single single) { - return single.toCompletionStage(); - } -} diff --git a/implementation/rxjava3/src/main/java/io/smallrye/faulttolerance/rxjava3/impl/SingleSupport.java b/implementation/rxjava3/src/main/java/io/smallrye/faulttolerance/rxjava3/impl/SingleSupport.java new file mode 100644 index 00000000..c3f8fe68 --- /dev/null +++ b/implementation/rxjava3/src/main/java/io/smallrye/faulttolerance/rxjava3/impl/SingleSupport.java @@ -0,0 +1,34 @@ +package io.smallrye.faulttolerance.rxjava3.impl; + +import java.util.concurrent.CompletionStage; + +import io.reactivex.rxjava3.core.Single; +import io.smallrye.faulttolerance.core.invocation.AsyncSupport; +import io.smallrye.faulttolerance.core.invocation.Invoker; + +public class SingleSupport implements AsyncSupport> { + @Override + public String description() { + return "return " + Single.class.getSimpleName(); + } + + @Override + public boolean applies(Class[] parameterTypes, Class returnType) { + return Single.class.equals(returnType); + } + + @Override + public CompletionStage toCompletionStage(Invoker> invoker) throws Exception { + return invoker.proceed().toCompletionStage(); + } + + @Override + public Single fromCompletionStage(Invoker> invoker) { + return Single.defer(() -> Single.fromCompletionStage(invoker.proceed())); + } + + @Override + public CompletionStage fallbackResultToCompletionStage(Single single) { + return single.toCompletionStage(); + } +} diff --git a/implementation/rxjava3/src/main/resources/META-INF/services/io.smallrye.faulttolerance.core.async.types.AsyncTypeConverter b/implementation/rxjava3/src/main/resources/META-INF/services/io.smallrye.faulttolerance.core.async.types.AsyncTypeConverter deleted file mode 100644 index 6d843cfb..00000000 --- a/implementation/rxjava3/src/main/resources/META-INF/services/io.smallrye.faulttolerance.core.async.types.AsyncTypeConverter +++ /dev/null @@ -1,3 +0,0 @@ -io.smallrye.faulttolerance.rxjava3.impl.SingleConverter -io.smallrye.faulttolerance.rxjava3.impl.MaybeConverter -io.smallrye.faulttolerance.rxjava3.impl.CompletableConverter \ No newline at end of file diff --git a/implementation/rxjava3/src/main/resources/META-INF/services/io.smallrye.faulttolerance.core.invocation.AsyncSupport b/implementation/rxjava3/src/main/resources/META-INF/services/io.smallrye.faulttolerance.core.invocation.AsyncSupport new file mode 100644 index 00000000..952ae257 --- /dev/null +++ b/implementation/rxjava3/src/main/resources/META-INF/services/io.smallrye.faulttolerance.core.invocation.AsyncSupport @@ -0,0 +1,3 @@ +io.smallrye.faulttolerance.rxjava3.impl.CompletableSupport +io.smallrye.faulttolerance.rxjava3.impl.MaybeSupport +io.smallrye.faulttolerance.rxjava3.impl.SingleSupport \ No newline at end of file diff --git a/pom.xml b/pom.xml index aff6d543..43bc0472 100644 --- a/pom.xml +++ b/pom.xml @@ -37,6 +37,8 @@ 1.13.0 3.4.3.Final 2.2.1.Final + 1.6.10 + 1.6.0 3.0 2.0 3.0 @@ -114,11 +116,6 @@ jakarta.interceptor-api ${version.jakarta-interceptors} - - com.squareup - javapoet - ${version.javapoet} - org.eclipse.microprofile.config microprofile-config-api @@ -151,22 +148,11 @@ ${version.microprofile-fault-tolerance} test + - org.jboss.logging - jboss-logging - ${version.jboss-logging} - - - org.jboss.logging - jboss-logging-annotations - ${version.jboss-logging-tools} - provided - - - org.jboss.logging - jboss-logging-processor - ${version.jboss-logging-tools} - provided + com.squareup + javapoet + ${version.javapoet} io.smallrye.common @@ -209,6 +195,28 @@ vertx-core ${version.vertx} + + org.jboss.logging + jboss-logging + ${version.jboss-logging} + + + org.jboss.logging + jboss-logging-annotations + ${version.jboss-logging-tools} + provided + + + org.jboss.logging + jboss-logging-processor + ${version.jboss-logging-tools} + provided + + + org.jetbrains.kotlin + kotlin-stdlib + ${version.kotlin} + io.smallrye.config @@ -234,6 +242,13 @@ ${version.smallrye-context-propagation} test + + org.jetbrains.kotlinx + kotlinx-coroutines-jdk8 + ${version.kotlinx-coroutines} + test + + org.assertj assertj-core @@ -324,6 +339,11 @@ test-jar ${project.version} + + io.smallrye + smallrye-fault-tolerance-kotlin + ${project.version} + io.smallrye smallrye-fault-tolerance-mutiny diff --git a/testsuite/basic/pom.xml b/testsuite/basic/pom.xml index 7f57cca3..e30e22d4 100644 --- a/testsuite/basic/pom.xml +++ b/testsuite/basic/pom.xml @@ -33,6 +33,11 @@ smallrye-fault-tolerance test + + io.smallrye + smallrye-fault-tolerance-kotlin + test + io.smallrye smallrye-fault-tolerance-mutiny @@ -76,6 +81,16 @@ rxjava test + + org.jetbrains.kotlin + kotlin-stdlib + test + + + org.jetbrains.kotlinx + kotlinx-coroutines-jdk8 + test + org.jboss.weld @@ -106,6 +121,67 @@ + + org.jetbrains.kotlin + kotlin-maven-plugin + ${version.kotlin} + + + compile + + compile + + + + ${project.basedir}/src/main/kotlin + ${project.basedir}/src/main/java + + + + + test-compile + + test-compile + + + + ${project.basedir}/src/test/kotlin + ${project.basedir}/src/test/java + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + + default-compile + none + + + + default-testCompile + none + + + java-compile + compile + + compile + + + + java-test-compile + test-compile + + testCompile + + + + org.apache.maven.plugins maven-surefire-plugin diff --git a/testsuite/basic/src/test/kotlin/io/smallrye/faulttolerance/kotlin/bulkhead/KotlinBulkheadTest.kt b/testsuite/basic/src/test/kotlin/io/smallrye/faulttolerance/kotlin/bulkhead/KotlinBulkheadTest.kt new file mode 100644 index 00000000..84e7f0a8 --- /dev/null +++ b/testsuite/basic/src/test/kotlin/io/smallrye/faulttolerance/kotlin/bulkhead/KotlinBulkheadTest.kt @@ -0,0 +1,36 @@ +package io.smallrye.faulttolerance.kotlin.bulkhead + +import io.smallrye.faulttolerance.util.FaultToleranceBasicTest +import kotlinx.coroutines.async +import kotlinx.coroutines.runBlocking +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test +import org.junitpioneer.jupiter.SetSystemProperty + +// so that FT methods don't have to be marked @NonBlocking +@SetSystemProperty(key = "smallrye.faulttolerance.mp-compatibility", value = "false") +@FaultToleranceBasicTest +class KotlinBulkheadTest { + private val tasks = 20_000 + + @Test + fun test(service: MyService) = runBlocking { + val results = (1..tasks).map { + async { + service.hello() + } + }.map { + it.await() + } + + val helloResults = results.count { it == "hello" } + val fallbackResults = results.count { it == "fallback" } + + // 10 immediately accepted into bulkhead + 10 queued + assertThat(helloResults).isEqualTo(20) + assertThat(MyService.helloCounter).hasValue(20) + + assertThat(fallbackResults).isEqualTo(tasks - 20) + assertThat(MyService.fallbackCounter).hasValue(tasks - 20) + } +} diff --git a/testsuite/basic/src/test/kotlin/io/smallrye/faulttolerance/kotlin/bulkhead/MyService.kt b/testsuite/basic/src/test/kotlin/io/smallrye/faulttolerance/kotlin/bulkhead/MyService.kt new file mode 100644 index 00000000..a3c1e9b3 --- /dev/null +++ b/testsuite/basic/src/test/kotlin/io/smallrye/faulttolerance/kotlin/bulkhead/MyService.kt @@ -0,0 +1,29 @@ +package io.smallrye.faulttolerance.kotlin.bulkhead + +import kotlinx.coroutines.delay +import org.eclipse.microprofile.faulttolerance.Bulkhead +import org.eclipse.microprofile.faulttolerance.Fallback +import java.util.concurrent.atomic.AtomicInteger +import javax.enterprise.context.ApplicationScoped + +@ApplicationScoped +open class MyService { + companion object { + val helloCounter = AtomicInteger(0) + val fallbackCounter = AtomicInteger(0) + } + + @Bulkhead + @Fallback(fallbackMethod = "fallback") + open suspend fun hello(): String { + helloCounter.incrementAndGet() + delay(2000) + return "hello" + } + + private suspend fun fallback(): String { + fallbackCounter.incrementAndGet() + delay(100) + return "fallback" + } +} diff --git a/testsuite/basic/src/test/kotlin/io/smallrye/faulttolerance/kotlin/circuitbreaker/KotlinCircuitBreakerTest.kt b/testsuite/basic/src/test/kotlin/io/smallrye/faulttolerance/kotlin/circuitbreaker/KotlinCircuitBreakerTest.kt new file mode 100644 index 00000000..973885e2 --- /dev/null +++ b/testsuite/basic/src/test/kotlin/io/smallrye/faulttolerance/kotlin/circuitbreaker/KotlinCircuitBreakerTest.kt @@ -0,0 +1,38 @@ +package io.smallrye.faulttolerance.kotlin.circuitbreaker + +import io.smallrye.faulttolerance.util.FaultToleranceBasicTest +import kotlinx.coroutines.delay +import kotlinx.coroutines.runBlocking +import org.assertj.core.api.Assertions.assertThat +import org.eclipse.microprofile.faulttolerance.exceptions.CircuitBreakerOpenException +import org.junit.jupiter.api.Test +import org.junitpioneer.jupiter.SetSystemProperty +import java.io.IOException + +// so that FT methods don't have to be marked @NonBlocking +@SetSystemProperty(key = "smallrye.faulttolerance.mp-compatibility", value = "false") +@FaultToleranceBasicTest +class KotlinCircuitBreakerTest { + @Test + fun test(service: MyService) = runBlocking { + for (i in 1..MyService.threshold) { + try { + service.hello(true) + } catch (e: Exception) { + assertThat(e).isExactlyInstanceOf(IOException::class.java) + } + } + + try { + service.hello(false) + } catch (e: Exception) { + assertThat(e).isExactlyInstanceOf(CircuitBreakerOpenException::class.java) + } + + assertThat(MyService.counter.get()).isEqualTo(MyService.threshold) + + delay(2 * MyService.delay) + + assertThat(service.hello(false)).isEqualTo("hello") + } +} diff --git a/testsuite/basic/src/test/kotlin/io/smallrye/faulttolerance/kotlin/circuitbreaker/MyService.kt b/testsuite/basic/src/test/kotlin/io/smallrye/faulttolerance/kotlin/circuitbreaker/MyService.kt new file mode 100644 index 00000000..ed2b5f3a --- /dev/null +++ b/testsuite/basic/src/test/kotlin/io/smallrye/faulttolerance/kotlin/circuitbreaker/MyService.kt @@ -0,0 +1,27 @@ +package io.smallrye.faulttolerance.kotlin.circuitbreaker + +import kotlinx.coroutines.delay +import org.eclipse.microprofile.faulttolerance.CircuitBreaker +import java.io.IOException +import java.util.concurrent.atomic.AtomicInteger +import javax.enterprise.context.ApplicationScoped + +@ApplicationScoped +open class MyService { + companion object { + const val threshold = 10 + const val delay = 500L + + val counter = AtomicInteger(0) + } + + @CircuitBreaker(requestVolumeThreshold = threshold, failureRatio = 0.5, delay = delay, successThreshold = 1) + open suspend fun hello(fail: Boolean): String { + counter.incrementAndGet() + delay(100) + if (fail) { + throw IOException() + } + return "hello" + } +} diff --git a/testsuite/basic/src/test/kotlin/io/smallrye/faulttolerance/kotlin/retry/KotlinRetryTest.kt b/testsuite/basic/src/test/kotlin/io/smallrye/faulttolerance/kotlin/retry/KotlinRetryTest.kt new file mode 100644 index 00000000..22a86e96 --- /dev/null +++ b/testsuite/basic/src/test/kotlin/io/smallrye/faulttolerance/kotlin/retry/KotlinRetryTest.kt @@ -0,0 +1,19 @@ +package io.smallrye.faulttolerance.kotlin.retry + +import io.smallrye.faulttolerance.util.FaultToleranceBasicTest +import kotlinx.coroutines.runBlocking +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test +import org.junitpioneer.jupiter.SetSystemProperty + +// so that FT methods don't have to be marked @NonBlocking +@SetSystemProperty(key = "smallrye.faulttolerance.mp-compatibility", value = "false") +@FaultToleranceBasicTest +class KotlinRetryTest { + @Test + fun test(service: MyService) = runBlocking { + val result = service.hello() + assertThat(result).isEqualTo(42) + assertThat(MyService.counter).hasValue(3) + } +} diff --git a/testsuite/basic/src/test/kotlin/io/smallrye/faulttolerance/kotlin/retry/MyService.kt b/testsuite/basic/src/test/kotlin/io/smallrye/faulttolerance/kotlin/retry/MyService.kt new file mode 100644 index 00000000..d8ded1db --- /dev/null +++ b/testsuite/basic/src/test/kotlin/io/smallrye/faulttolerance/kotlin/retry/MyService.kt @@ -0,0 +1,28 @@ +package io.smallrye.faulttolerance.kotlin.retry + +import kotlinx.coroutines.delay +import org.eclipse.microprofile.faulttolerance.Fallback +import org.eclipse.microprofile.faulttolerance.Retry +import java.util.concurrent.atomic.AtomicInteger +import javax.enterprise.context.ApplicationScoped + +@ApplicationScoped +open class MyService { + companion object { + val counter = AtomicInteger(0) + } + + @Retry(maxRetries = 2) + @Fallback(fallbackMethod = "helloFallback") + open suspend fun hello(): Int { + delay(100) + counter.incrementAndGet() + delay(100) + throw IllegalArgumentException() + } + + private suspend fun helloFallback(): Int { + delay(100) + return 42 + } +} diff --git a/testsuite/basic/src/test/kotlin/io/smallrye/faulttolerance/kotlin/timeout/KotlinTimeoutTest.kt b/testsuite/basic/src/test/kotlin/io/smallrye/faulttolerance/kotlin/timeout/KotlinTimeoutTest.kt new file mode 100644 index 00000000..e8787ed4 --- /dev/null +++ b/testsuite/basic/src/test/kotlin/io/smallrye/faulttolerance/kotlin/timeout/KotlinTimeoutTest.kt @@ -0,0 +1,18 @@ +package io.smallrye.faulttolerance.kotlin.timeout + +import io.smallrye.faulttolerance.util.FaultToleranceBasicTest +import kotlinx.coroutines.runBlocking +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test +import org.junitpioneer.jupiter.SetSystemProperty + +// so that FT methods don't have to be marked @NonBlocking +@SetSystemProperty(key = "smallrye.faulttolerance.mp-compatibility", value = "false") +@FaultToleranceBasicTest +class KotlinTimeoutTest { + @Test + fun test(service: MyService) = runBlocking { + val result = service.hello() + assertThat(result).isEqualTo(42) + } +} diff --git a/testsuite/basic/src/test/kotlin/io/smallrye/faulttolerance/kotlin/timeout/MyService.kt b/testsuite/basic/src/test/kotlin/io/smallrye/faulttolerance/kotlin/timeout/MyService.kt new file mode 100644 index 00000000..cd7224ff --- /dev/null +++ b/testsuite/basic/src/test/kotlin/io/smallrye/faulttolerance/kotlin/timeout/MyService.kt @@ -0,0 +1,22 @@ +package io.smallrye.faulttolerance.kotlin.timeout + +import kotlinx.coroutines.delay +import org.eclipse.microprofile.faulttolerance.ExecutionContext +import org.eclipse.microprofile.faulttolerance.Fallback +import org.eclipse.microprofile.faulttolerance.FallbackHandler +import org.eclipse.microprofile.faulttolerance.Timeout +import javax.enterprise.context.ApplicationScoped + +@ApplicationScoped +open class MyService { + @Timeout(500) + @Fallback(MyFallbackHandler::class) + open suspend fun hello(): Int { + delay(2000) + return 13 + } + + class MyFallbackHandler : FallbackHandler { + override fun handle(context: ExecutionContext) = 42 + } +} From 798a919e36973cf343d618722ef8a1f463c2706a Mon Sep 17 00:00:00 2001 From: Ladislav Thon Date: Mon, 28 Mar 2022 13:39:47 +0200 Subject: [PATCH 58/68] add test for interface-based beans and default fallback methods The implementation approach is adapted from RESTEasy implementation of MicroProfile RestClient. --- .../defaultmethod/DefaultMethodTest.java | 19 ++ .../defaultmethod/HelloService.java | 15 ++ .../InterceptingInvocationHandler.java | 204 ++++++++++++++++++ .../defaultmethod/InterfaceBased.java | 18 ++ .../InterfaceBasedExtension.java | 60 ++++++ .../defaultmethod/RegisterInterfaceBased.java | 11 + .../defaultmethod/SimpleService.java | 13 ++ 7 files changed, 340 insertions(+) create mode 100644 testsuite/basic/src/test/java/io/smallrye/faulttolerance/defaultmethod/DefaultMethodTest.java create mode 100644 testsuite/basic/src/test/java/io/smallrye/faulttolerance/defaultmethod/HelloService.java create mode 100644 testsuite/basic/src/test/java/io/smallrye/faulttolerance/defaultmethod/InterceptingInvocationHandler.java create mode 100644 testsuite/basic/src/test/java/io/smallrye/faulttolerance/defaultmethod/InterfaceBased.java create mode 100644 testsuite/basic/src/test/java/io/smallrye/faulttolerance/defaultmethod/InterfaceBasedExtension.java create mode 100644 testsuite/basic/src/test/java/io/smallrye/faulttolerance/defaultmethod/RegisterInterfaceBased.java create mode 100644 testsuite/basic/src/test/java/io/smallrye/faulttolerance/defaultmethod/SimpleService.java diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/defaultmethod/DefaultMethodTest.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/defaultmethod/DefaultMethodTest.java new file mode 100644 index 00000000..ecb57316 --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/defaultmethod/DefaultMethodTest.java @@ -0,0 +1,19 @@ +package io.smallrye.faulttolerance.defaultmethod; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.jboss.weld.junit5.auto.AddBeanClasses; +import org.jboss.weld.junit5.auto.AddExtensions; +import org.junit.jupiter.api.Test; + +import io.smallrye.faulttolerance.util.FaultToleranceBasicTest; + +@FaultToleranceBasicTest +@AddExtensions(InterfaceBasedExtension.class) +@AddBeanClasses(SimpleService.class) +public class DefaultMethodTest { + @Test + public void test(HelloService service) { + assertThat(service.hello()).isEqualTo("Hello, world!"); + } +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/defaultmethod/HelloService.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/defaultmethod/HelloService.java new file mode 100644 index 00000000..7979ca8a --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/defaultmethod/HelloService.java @@ -0,0 +1,15 @@ +package io.smallrye.faulttolerance.defaultmethod; + +import javax.enterprise.context.ApplicationScoped; +import javax.inject.Inject; + +@ApplicationScoped +public class HelloService { + @Inject + @InterfaceBased + private SimpleService service; + + public String hello() { + return service.hello(); + } +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/defaultmethod/InterceptingInvocationHandler.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/defaultmethod/InterceptingInvocationHandler.java new file mode 100644 index 00000000..00d81758 --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/defaultmethod/InterceptingInvocationHandler.java @@ -0,0 +1,204 @@ +package io.smallrye.faulttolerance.defaultmethod; + +import static io.smallrye.faulttolerance.core.util.SneakyThrow.sneakyThrow; + +import java.lang.annotation.Annotation; +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import javax.enterprise.context.spi.CreationalContext; +import javax.enterprise.inject.spi.BeanManager; +import javax.enterprise.inject.spi.InterceptionType; +import javax.enterprise.inject.spi.Interceptor; +import javax.interceptor.InvocationContext; + +// adapted from https://github.com/resteasy/resteasy-microprofile/tree/main/rest-client-base/src/main/java/org/jboss/resteasy/microprofile/client +// which is Apache 2.0 licensed +public class InterceptingInvocationHandler implements InvocationHandler { + private final Object target; + + private final Map> interceptorChains; + + public InterceptingInvocationHandler(Class iface, Object target, BeanManager beanManager) { + this.target = target; + + // this CreationalContext is never released, but that's ok _in a test_ + CreationalContext creationalContext = beanManager.createCreationalContext(null); + this.interceptorChains = initInterceptorChains(beanManager, creationalContext, iface); + } + + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + List chain = interceptorChains.get(method); + if (chain != null) { + return new InvocationContextImpl(target, method, args, chain).proceed(); + } else { + try { + return method.invoke(target, args); + } catch (InvocationTargetException e) { + throw e.getCause(); + } + } + } + + private static Map> initInterceptorChains(BeanManager beanManager, + CreationalContext creationalContext, Class iface) { + + Map> chains = new HashMap<>(); + Map, Object> interceptorInstances = new HashMap<>(); + + List classLevelBindings = getBindings(iface, beanManager); + + for (Method method : iface.getMethods()) { + if (method.isDefault() || Modifier.isStatic(method.getModifiers())) { + continue; + } + + List methodLevelBindings = getBindings(method, beanManager); + if (!classLevelBindings.isEmpty() || !methodLevelBindings.isEmpty()) { + Annotation[] interceptorBindings = mergeBindings(methodLevelBindings, classLevelBindings); + + List> interceptors = beanManager.resolveInterceptors(InterceptionType.AROUND_INVOKE, + interceptorBindings); + if (!interceptors.isEmpty()) { + List chain = new ArrayList<>(); + for (Interceptor interceptor : interceptors) { + chain.add(new InterceptorInvocation((Interceptor) interceptor, + interceptorInstances.computeIfAbsent(interceptor, + i -> beanManager.getReference(i, i.getBeanClass(), creationalContext)))); + } + chains.put(method, chain); + } + } + } + + return chains; + } + + private static List getBindings(AnnotatedElement annotated, BeanManager beanManager) { + Annotation[] annotations = annotated.getAnnotations(); + if (annotations.length == 0) { + return Collections.emptyList(); + } + List bindings = new ArrayList<>(); + for (Annotation annotation : annotations) { + if (beanManager.isInterceptorBinding(annotation.annotationType())) { + bindings.add(annotation); + } + } + return bindings; + } + + private static Annotation[] mergeBindings(List methodLevel, List classLevel) { + Set> methodLevelTypes = methodLevel.stream() + .map(Annotation::annotationType) + .collect(Collectors.toSet()); + + List result = new ArrayList<>(methodLevel); + for (Annotation classLevelBinding : classLevel) { + if (!methodLevelTypes.contains(classLevelBinding.annotationType())) { + result.add(classLevelBinding); + } + } + return result.toArray(new Annotation[0]); + } + + private static class InvocationContextImpl implements InvocationContext { + private final Object target; + private final Method method; + private Object[] arguments; + private final Map contextData; + + private final List chain; + private final int position; + + public InvocationContextImpl(Object target, Method method, Object[] arguments, + List chain) { + this(target, method, arguments, chain, 0); + } + + private InvocationContextImpl(Object target, Method method, Object[] arguments, + List chain, int position) { + this.target = target; + this.method = method; + this.arguments = arguments; + this.contextData = new HashMap<>(); + this.chain = chain; + this.position = position; + } + + @Override + public Object proceed() throws Exception { + try { + if (position < chain.size()) { + InvocationContextImpl ctx = new InvocationContextImpl(target, method, arguments, chain, position + 1); + return chain.get(position).invoke(ctx); + } else { + return method.invoke(target, arguments); + } + } catch (InvocationTargetException e) { + throw sneakyThrow(e.getCause()); + } + } + + @Override + public Object getTarget() { + return target; + } + + @Override + public Method getMethod() { + return method; + } + + @Override + public Constructor getConstructor() { + return null; + } + + @Override + public Object[] getParameters() throws IllegalStateException { + return arguments; + } + + @Override + public void setParameters(Object[] args) throws IllegalStateException, IllegalArgumentException { + this.arguments = args; + } + + @Override + public Map getContextData() { + return contextData; + } + + @Override + public Object getTimer() { + return null; + } + } + + private static class InterceptorInvocation { + private final Interceptor interceptorBean; + private final Object interceptorInstance; + + public InterceptorInvocation(Interceptor interceptorBean, Object interceptorInstance) { + this.interceptorBean = interceptorBean; + this.interceptorInstance = interceptorInstance; + } + + public Object invoke(InvocationContext ctx) throws Exception { + return interceptorBean.intercept(InterceptionType.AROUND_INVOKE, interceptorInstance, ctx); + } + } +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/defaultmethod/InterfaceBased.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/defaultmethod/InterfaceBased.java new file mode 100644 index 00000000..be8623bf --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/defaultmethod/InterfaceBased.java @@ -0,0 +1,18 @@ +package io.smallrye.faulttolerance.defaultmethod; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import javax.enterprise.util.AnnotationLiteral; +import javax.inject.Qualifier; + +@Qualifier +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.RUNTIME) +public @interface InterfaceBased { + final class Literal extends AnnotationLiteral implements InterfaceBased { + public static final Literal INSTANCE = new Literal(); + } +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/defaultmethod/InterfaceBasedExtension.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/defaultmethod/InterfaceBasedExtension.java new file mode 100644 index 00000000..7de59ddb --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/defaultmethod/InterfaceBasedExtension.java @@ -0,0 +1,60 @@ +package io.smallrye.faulttolerance.defaultmethod; + +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Logger; + +import javax.enterprise.context.Dependent; +import javax.enterprise.event.Observes; +import javax.enterprise.inject.Any; +import javax.enterprise.inject.Default; +import javax.enterprise.inject.spi.AfterBeanDiscovery; +import javax.enterprise.inject.spi.BeanManager; +import javax.enterprise.inject.spi.BeforeBeanDiscovery; +import javax.enterprise.inject.spi.Extension; +import javax.enterprise.inject.spi.ProcessAnnotatedType; +import javax.enterprise.inject.spi.WithAnnotations; + +public class InterfaceBasedExtension implements Extension { + private static final Logger LOG = Logger.getLogger(InterfaceBasedExtension.class.getName()); + + private final List> interfaces = new ArrayList<>(); + + private void init(@Observes BeforeBeanDiscovery bbd, BeanManager manager) { + LOG.info(InterfaceBasedExtension.class.getSimpleName() + " initialized"); + } + + public void rememberInterfaces(@Observes @WithAnnotations(RegisterInterfaceBased.class) ProcessAnnotatedType pat) { + Class clazz = pat.getAnnotatedType().getJavaClass(); + if (clazz.isInterface()) { + interfaces.add(clazz); + LOG.info("Found " + clazz); + pat.veto(); + } else { + throw new IllegalArgumentException("Must be an interface: " + clazz); + } + } + + public void registerBeans(@Observes AfterBeanDiscovery abd, BeanManager beanManager) { + for (Class iface : interfaces) { + abd.addBean() + .beanClass(iface) + .types(iface) + .scope(Dependent.class) + .qualifiers(Default.Literal.INSTANCE, Any.Literal.INSTANCE, InterfaceBased.Literal.INSTANCE) + .createWith(ctx -> { + Object target = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), + new Class[] { iface }, InterfaceBasedExtension::invoke); + return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), + new Class[] { iface }, new InterceptingInvocationHandler(iface, target, beanManager)); + }); + LOG.info("Registered bean for " + iface); + } + } + + private static Object invoke(Object proxy, Method method, Object[] args) { + throw new IllegalArgumentException("Always an exception"); + } +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/defaultmethod/RegisterInterfaceBased.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/defaultmethod/RegisterInterfaceBased.java new file mode 100644 index 00000000..0c6d6af2 --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/defaultmethod/RegisterInterfaceBased.java @@ -0,0 +1,11 @@ +package io.smallrye.faulttolerance.defaultmethod; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +public @interface RegisterInterfaceBased { +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/defaultmethod/SimpleService.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/defaultmethod/SimpleService.java new file mode 100644 index 00000000..f3773b43 --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/defaultmethod/SimpleService.java @@ -0,0 +1,13 @@ +package io.smallrye.faulttolerance.defaultmethod; + +import org.eclipse.microprofile.faulttolerance.Fallback; + +@RegisterInterfaceBased +public interface SimpleService { + @Fallback(fallbackMethod = "fallback") + String hello(); + + default String fallback() { + return "Hello, world!"; + } +} From ee55decc6837844f3c317d5720a16fc1e85519a4 Mon Sep 17 00:00:00 2001 From: Ladislav Thon Date: Tue, 29 Mar 2022 17:52:08 +0200 Subject: [PATCH 59/68] initial support for preconfigured, reusable fault tolerance --- .../api/ApplyFaultTolerance.java | 93 +++++++++++++++++++ .../faulttolerance/api/FaultTolerance.java | 8 +- doc/modules/ROOT/pages/usage/extra.adoc | 51 ++++++++++ .../autoconfig/FaultToleranceMethod.java | 2 + .../core/apiimpl/FaultToleranceImpl.java | 4 + .../core/invocation/AsyncSupport.java | 4 +- .../invocation/CompletionStageSupport.java | 7 +- .../FaultToleranceExtension.java | 3 + .../FaultToleranceInterceptor.java | 54 ++++++++++- .../faulttolerance/SpecCompatibility.java | 1 - .../config/ApplyFaultToleranceConfig.java | 20 ++++ .../config/AsynchronousConfig.java | 2 +- .../config/FaultToleranceMethods.java | 3 + .../config/FaultToleranceOperation.java | 19 ++++ .../internal/InterceptionPoint.java | 12 +-- .../kotlin/impl/CoroutineSupport.kt | 6 +- .../mutiny/impl/UniSupport.java | 7 +- .../rxjava3/impl/CompletableSupport.java | 7 +- .../rxjava3/impl/MaybeSupport.java | 7 +- .../rxjava3/impl/SingleSupport.java | 7 +- .../completionstage/MyFaultTolerance.java | 21 +++++ .../async/completionstage/MyService.java | 21 +++++ .../ReuseAsyncCompletionStageTest.java | 19 ++++ .../reuse/async/uni/MyFaultTolerance.java | 19 ++++ .../reuse/async/uni/MyService.java | 19 ++++ .../reuse/async/uni/ReuseAsyncUniTest.java | 17 ++++ .../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 +++++ .../sync/vs/async/MyFaultTolerance.java | 14 +++ .../mismatch/sync/vs/async/MyService.java | 15 +++ .../async/ReuseMismatchSyncVsAsyncTest.java | 21 +++++ .../reuse/missing/MyService.java | 13 +++ .../reuse/missing/ReuseMissingTest.java | 19 ++++ .../MixedReuseAsyncCompletionStageTest.java | 23 +++++ .../completionstage/MyFaultTolerance.java | 19 ++++ .../async/completionstage/MyService.java | 33 +++++++ .../async/uni/MixedReuseAsyncUniTest.java | 23 +++++ .../mixed/async/uni/MyFaultTolerance.java | 19 ++++ .../reuse/mixed/async/uni/MyService.java | 30 ++++++ .../reuse/mixed/sync/MixedReuseSyncTest.java | 21 +++++ .../reuse/mixed/sync/MyFaultTolerance.java | 17 ++++ .../reuse/mixed/sync/MyService.java | 29 ++++++ .../reuse/sync/MyFaultTolerance.java | 17 ++++ .../faulttolerance/reuse/sync/MyService.java | 18 ++++ .../reuse/sync/ReuseSyncTest.java | 17 ++++ .../kotlin/reuse/MyFaultTolerance.kt | 17 ++++ .../faulttolerance/kotlin/reuse/MyService.kt | 19 ++++ .../kotlin/reuse/ReuseKotlinTest.kt | 15 +++ 52 files changed, 912 insertions(+), 24 deletions(-) create mode 100644 api/src/main/java/io/smallrye/faulttolerance/api/ApplyFaultTolerance.java create mode 100644 implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/config/ApplyFaultToleranceConfig.java create mode 100644 testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/completionstage/MyFaultTolerance.java create mode 100644 testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/completionstage/MyService.java create mode 100644 testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/completionstage/ReuseAsyncCompletionStageTest.java create mode 100644 testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/uni/MyFaultTolerance.java create mode 100644 testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/uni/MyService.java create mode 100644 testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/uni/ReuseAsyncUniTest.java create mode 100644 testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mismatch/async/vs/async/MyFaultTolerance.java create mode 100644 testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mismatch/async/vs/async/MyService.java create mode 100644 testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mismatch/async/vs/async/ReuseMismatchAsyncVsAsyncTest.java create mode 100644 testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mismatch/async/vs/sync/MyFaultTolerance.java create mode 100644 testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mismatch/async/vs/sync/MyService.java create mode 100644 testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mismatch/async/vs/sync/ReuseMismatchAsyncVsSyncTest.java create mode 100644 testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mismatch/sync/vs/async/MyFaultTolerance.java create mode 100644 testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mismatch/sync/vs/async/MyService.java create 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/missing/MyService.java create mode 100644 testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/missing/ReuseMissingTest.java create mode 100644 testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/async/completionstage/MixedReuseAsyncCompletionStageTest.java create mode 100644 testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/async/completionstage/MyFaultTolerance.java create mode 100644 testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/async/completionstage/MyService.java create mode 100644 testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/async/uni/MixedReuseAsyncUniTest.java create mode 100644 testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/async/uni/MyFaultTolerance.java create mode 100644 testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/async/uni/MyService.java create mode 100644 testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/sync/MixedReuseSyncTest.java create mode 100644 testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/sync/MyFaultTolerance.java create mode 100644 testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/sync/MyService.java create mode 100644 testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/sync/MyFaultTolerance.java create mode 100644 testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/sync/MyService.java create mode 100644 testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/sync/ReuseSyncTest.java create mode 100644 testsuite/basic/src/test/kotlin/io/smallrye/faulttolerance/kotlin/reuse/MyFaultTolerance.kt create mode 100644 testsuite/basic/src/test/kotlin/io/smallrye/faulttolerance/kotlin/reuse/MyService.kt create mode 100644 testsuite/basic/src/test/kotlin/io/smallrye/faulttolerance/kotlin/reuse/ReuseKotlinTest.kt diff --git a/api/src/main/java/io/smallrye/faulttolerance/api/ApplyFaultTolerance.java b/api/src/main/java/io/smallrye/faulttolerance/api/ApplyFaultTolerance.java new file mode 100644 index 00000000..f05fb312 --- /dev/null +++ b/api/src/main/java/io/smallrye/faulttolerance/api/ApplyFaultTolerance.java @@ -0,0 +1,93 @@ +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 javax.interceptor.InterceptorBinding; + +import io.smallrye.common.annotation.Experimental; + +/** + * A special interceptor binding annotation to apply preconfigured fault tolerance. + * If {@code @ApplyFaultTolerance("<identifier>")} 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). + *

+ * 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. + *

+ * + * It is customary to create such bean by declaring a {@code static} producer field. + * For example: + * + * + *

{@code
+ * @ApplicationScoped
+ * public class PreconfiguredFaultTolerance {
+ *     @Produces
+ *     @Identifier("my-fault-tolerance")
+ *     public static final FaultTolerance FT = FaultTolerance.create()
+ *             .withRetry().maxRetries(2).done()
+ *             .withFallback().handler(() -> "fallback").done()
+ *             .build();
+ * }
+ * }
+ * + * + * Using a {@code static} producer field like this 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. + *

+ * Example of use: + * + * + *

{@code
+ * @ApplicationScoped
+ * public class MyService {
+ *     @ApplyFaultTolerance("my-fault-tolerance")
+ *     public String doSomething() {
+ *         ...
+ *     }
+ * }
+ * }
+ * + * + * 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<Object>} + * for synchronous methods, {@code FaultTolerance<CompletionStage<Object>>} 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. + */ +@Inherited +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.METHOD, ElementType.TYPE }) +@InterceptorBinding +@Experimental("first attempt at providing reusable fault tolerance strategies") +public @interface ApplyFaultTolerance { + /** + * The identifier of a preconfigured fault tolerance instance. + */ + String value(); +} 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 b9cd7b9c..d22e90f4 100644 --- a/api/src/main/java/io/smallrye/faulttolerance/api/FaultTolerance.java +++ b/api/src/main/java/io/smallrye/faulttolerance/api/FaultTolerance.java @@ -312,8 +312,8 @@ interface Builder { * Syntactic sugar for calling the builder methods conditionally without breaking the invocation chain. * For example: * - *

-         * {@code
+         * 
+         * 
{@code
          * FaultTolerance.create(this::doSomething)
          *     .withFallback() ... .done()
          *     .with(builder -> {
@@ -322,8 +322,8 @@ interface Builder {
          *         }
          *     })
          *     .build();
-         * }
-         * 
+ * }
+ * * * @param consumer block of code to execute with this builder * @return this fault tolerance builder diff --git a/doc/modules/ROOT/pages/usage/extra.adoc b/doc/modules/ROOT/pages/usage/extra.adoc index f54b02ec..64eaf806 100644 --- a/doc/modules/ROOT/pages/usage/extra.adoc +++ b/doc/modules/ROOT/pages/usage/extra.adoc @@ -470,3 +470,54 @@ You can declare fault tolerance annotations on suspending methods out of the box Suspending functions are currently only supported in the declarative, annotation-based API, as shown in the example above. The xref:usage/programmatic-api.adoc[Programmatic API] of {smallrye-fault-tolerance} does not support suspending functions, but other than that, it can of course be used from Kotlin through its Java interop. + +== Reusable, Preconfigured Fault Tolerance + +The declarative, annotation-based API of {microprofile-fault-tolerance} doesn't allow sharing configuration of fault tolerance strategies across multiple classes. +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 and/or circuit breaker, which is often not what you want. + +The xref:usage/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. + +To be able to do that, we need a bean of type `FaultTolerance` with the `@Identifier` qualifier: + +[source,java] +---- +@ApplicationScoped +public class PreconfiguredFaultTolerance { + @Produces + @Identifier("my-fault-tolerance") + public static final FaultTolerance FT = FaultTolerance.create() + .withRetry().maxRetries(2).done() + .withFallback().handler(() -> "fallback").done() + .build(); +} +---- + +See the xref:usage/programmatic-api.adoc[programmatic API] documentation for more information about creating the `FaultTolerance` 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`: + +[source,java] +---- +@ApplicationScoped +public class MyService { + @ApplyFaultTolerance("my-fault-tolerance") + public String doSomething() { + ... + } +} +---- + +It is also possible to create a bean of type `FaultTolerance` and apply it to synchronous methods that return many 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 <>. + +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 <>. +This limitation will be lifted in the future. 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 740e4f2d..ea8db72d 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 @@ -12,6 +12,7 @@ import io.smallrye.common.annotation.Blocking; import io.smallrye.common.annotation.NonBlocking; +import io.smallrye.faulttolerance.api.ApplyFaultTolerance; import io.smallrye.faulttolerance.api.CircuitBreakerName; import io.smallrye.faulttolerance.api.CustomBackoff; import io.smallrye.faulttolerance.api.ExponentialBackoff; @@ -45,6 +46,7 @@ public class FaultToleranceMethod { public Timeout timeout; // SmallRye Fault Tolerance API + public ApplyFaultTolerance applyFaultTolerance; public CircuitBreakerName circuitBreakerName; public CustomBackoff customBackoff; public ExponentialBackoff exponentialBackoff; 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 2bb6c102..bf63b214 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 @@ -101,6 +101,10 @@ public final class FaultToleranceImpl implements FaultTolerance { this.initializer = initializer; } + public AsyncSupport internalGetAsyncSupport() { + return asyncSupport; + } + @Override public T call(Callable action) throws Exception { initializer.runOnce(); diff --git a/implementation/core/src/main/java/io/smallrye/faulttolerance/core/invocation/AsyncSupport.java b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/invocation/AsyncSupport.java index 095ad610..31bd097c 100644 --- a/implementation/core/src/main/java/io/smallrye/faulttolerance/core/invocation/AsyncSupport.java +++ b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/invocation/AsyncSupport.java @@ -5,7 +5,9 @@ // V = value type, e.g. String // AT = async type that eventually produces V, e.g. CompletionStage or Uni public interface AsyncSupport { - String description(); + String mustDescription(); + + String doesDescription(); boolean applies(Class[] parameterTypes, Class returnType); diff --git a/implementation/core/src/main/java/io/smallrye/faulttolerance/core/invocation/CompletionStageSupport.java b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/invocation/CompletionStageSupport.java index 73b9e47b..992816df 100644 --- a/implementation/core/src/main/java/io/smallrye/faulttolerance/core/invocation/CompletionStageSupport.java +++ b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/invocation/CompletionStageSupport.java @@ -6,10 +6,15 @@ public class CompletionStageSupport implements AsyncSupport> { @Override - public String description() { + public String mustDescription() { return "return " + CompletionStage.class.getSimpleName(); } + @Override + public String doesDescription() { + return "returns " + CompletionStage.class.getSimpleName(); + } + @Override public boolean applies(Class[] parameterTypes, Class returnType) { return CompletionStage.class.equals(returnType); 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 204e1d4b..3ebd01e3 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 @@ -59,6 +59,7 @@ import io.smallrye.common.annotation.Blocking; import io.smallrye.common.annotation.NonBlocking; +import io.smallrye.faulttolerance.api.ApplyFaultTolerance; import io.smallrye.faulttolerance.api.CustomBackoff; import io.smallrye.faulttolerance.api.ExponentialBackoff; import io.smallrye.faulttolerance.api.FibonacciBackoff; @@ -97,6 +98,8 @@ void registerInterceptorBindings(@Observes BeforeBeanDiscovery bbd, BeanManager bbd.addInterceptorBinding(new FTInterceptorBindingAnnotatedType<>(bm.createAnnotatedType(Fallback.class))); bbd.addInterceptorBinding(new FTInterceptorBindingAnnotatedType<>(bm.createAnnotatedType(Bulkhead.class))); + bbd.addInterceptorBinding(new FTInterceptorBindingAnnotatedType<>(bm.createAnnotatedType(ApplyFaultTolerance.class))); + bbd.addAnnotatedType(bm.createAnnotatedType(FaultToleranceInterceptor.class), FaultToleranceInterceptor.class.getName()); bbd.addAnnotatedType(bm.createAnnotatedType(DefaultFallbackHandlerProvider.class), 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 6e85dcca..6d28ed13 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 @@ -35,6 +35,8 @@ import javax.annotation.Priority; import javax.enterprise.context.control.RequestContextController; +import javax.enterprise.inject.Any; +import javax.enterprise.inject.Instance; import javax.enterprise.inject.Intercepted; import javax.enterprise.inject.spi.Bean; import javax.inject.Inject; @@ -45,9 +47,12 @@ import org.eclipse.microprofile.faulttolerance.FallbackHandler; import org.eclipse.microprofile.faulttolerance.exceptions.FaultToleranceException; +import io.smallrye.common.annotation.Identifier; import io.smallrye.faulttolerance.api.CustomBackoffStrategy; +import io.smallrye.faulttolerance.api.FaultTolerance; import io.smallrye.faulttolerance.config.FaultToleranceOperation; import io.smallrye.faulttolerance.core.FaultToleranceStrategy; +import io.smallrye.faulttolerance.core.apiimpl.FaultToleranceImpl; import io.smallrye.faulttolerance.core.async.CompletionStageExecution; import io.smallrye.faulttolerance.core.async.FutureExecution; import io.smallrye.faulttolerance.core.async.RememberEventLoop; @@ -132,6 +137,8 @@ public class FaultToleranceInterceptor { private final SpecCompatibility specCompatibility; + private final Instance> configuredFaultTolerance; + @Inject public FaultToleranceInterceptor( @Intercepted Bean interceptedBean, @@ -142,7 +149,8 @@ public FaultToleranceInterceptor( ExecutorHolder executorHolder, RequestContextIntegration requestContextIntegration, CircuitBreakerMaintenanceImpl cbMaintenance, - SpecCompatibility specCompatibility) { + SpecCompatibility specCompatibility, + @Any Instance> configuredFaultTolerance) { this.interceptedBean = interceptedBean; this.operationProvider = operationProvider; this.cache = cache; @@ -154,17 +162,19 @@ public FaultToleranceInterceptor( requestContextController = requestContextIntegration.get(); this.cbMaintenance = cbMaintenance; this.specCompatibility = specCompatibility; + this.configuredFaultTolerance = configuredFaultTolerance; } @AroundInvoke public Object intercept(InvocationContext interceptionContext) throws Exception { Method method = interceptionContext.getMethod(); Class beanClass = interceptedBean != null ? interceptedBean.getBeanClass() : method.getDeclaringClass(); - InterceptionPoint point = new InterceptionPoint(beanClass, interceptionContext); - + InterceptionPoint point = new InterceptionPoint(beanClass, interceptionContext.getMethod()); FaultToleranceOperation operation = operationProvider.get(beanClass, method); - if (specCompatibility.isOperationTrulyAsynchronous(operation)) { + if (operation.hasApplyFaultTolerance()) { + return preconfiguredFlow(operation, interceptionContext); + } else if (specCompatibility.isOperationTrulyAsynchronous(operation)) { return asyncFlow(operation, interceptionContext, point); } else if (specCompatibility.isOperationPseudoAsynchronous(operation)) { return futureFlow(operation, interceptionContext, point); @@ -173,6 +183,42 @@ public Object intercept(InvocationContext interceptionContext) throws Exception } } + private Object preconfiguredFlow(FaultToleranceOperation operation, InvocationContext interceptionContext) + throws Exception { + String identifier = operation.getApplyFaultTolerance().value(); + Instance> instance = configuredFaultTolerance.select(Identifier.Literal.of(identifier)); + if (!instance.isResolvable()) { + throw new FaultToleranceException("Can't resolve a bean of type " + FaultTolerance.class.getName() + + " with qualifier @" + Identifier.class.getName() + "(\"" + identifier + "\")"); + } + FaultTolerance faultTolerance = (FaultTolerance) instance.get(); + if (!(faultTolerance instanceof FaultToleranceImpl)) { + throw new FaultToleranceException("Configured fault tolerance '" + identifier + + "' is not created by the FaultTolerance API, this is not supported"); + } + + AsyncSupport forOperation = AsyncSupportRegistry.get(operation.getParameterTypes(), operation.getReturnType()); + AsyncSupport fromConfigured = ((FaultToleranceImpl) faultTolerance).internalGetAsyncSupport(); + + if (forOperation == null && fromConfigured == null) { + return faultTolerance.call(interceptionContext::proceed); + } else if (forOperation == null) { + throw new FaultToleranceException("Configured fault tolerance '" + identifier + + "' expects the operation to " + fromConfigured.mustDescription() + + ", but the operation is synchronous: " + operation); + } else if (fromConfigured == null) { + throw new FaultToleranceException("Configured fault tolerance '" + identifier + + "' expects the operation to be synchronous, but it " + + forOperation.doesDescription() + ": " + operation); + } else if (!forOperation.getClass().equals(fromConfigured.getClass())) { + throw new FaultToleranceException("Configured fault tolerance '" + identifier + + "' expects the operation to " + fromConfigured.mustDescription() + + ", but it " + forOperation.doesDescription() + ": " + operation); + } else { + return faultTolerance.call(interceptionContext::proceed); + } + } + private AT asyncFlow(FaultToleranceOperation operation, InvocationContext interceptionContext, InterceptionPoint point) { AsyncSupport asyncSupport = AsyncSupportRegistry.get(operation.getParameterTypes(), operation.getReturnType()); diff --git a/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/SpecCompatibility.java b/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/SpecCompatibility.java index c45b39fe..9459e680 100644 --- a/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/SpecCompatibility.java +++ b/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/SpecCompatibility.java @@ -21,7 +21,6 @@ public SpecCompatibility( } public boolean isOperationTrulyAsynchronous(FaultToleranceOperation operation) { - //boolean supported = AsyncTypes.isKnown(operation.getReturnType()); boolean supported = AsyncSupportRegistry.isKnown(operation.getParameterTypes(), operation.getReturnType()); if (compatible) { diff --git a/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/config/ApplyFaultToleranceConfig.java b/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/config/ApplyFaultToleranceConfig.java new file mode 100644 index 00000000..d826d9c4 --- /dev/null +++ b/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/config/ApplyFaultToleranceConfig.java @@ -0,0 +1,20 @@ +package io.smallrye.faulttolerance.config; + +import org.eclipse.microprofile.faulttolerance.exceptions.FaultToleranceDefinitionException; + +import io.smallrye.faulttolerance.api.ApplyFaultTolerance; +import io.smallrye.faulttolerance.autoconfig.AutoConfig; +import io.smallrye.faulttolerance.autoconfig.Config; + +@AutoConfig +public interface ApplyFaultToleranceConfig extends ApplyFaultTolerance, Config { + @Override + default void validate() { + final String INVALID_APPLY_FAULT_TOLERANCE_ON = "Invalid @ApplyFaultTolerance on "; + + if (value().isEmpty()) { + throw new FaultToleranceDefinitionException(INVALID_APPLY_FAULT_TOLERANCE_ON + method() + + ": value shouldn't be empty"); + } + } +} diff --git a/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/config/AsynchronousConfig.java b/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/config/AsynchronousConfig.java index 321e7243..6fb9bc03 100644 --- a/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/config/AsynchronousConfig.java +++ b/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/config/AsynchronousConfig.java @@ -23,7 +23,7 @@ default void validate() { StringJoiner knownAsync = new StringJoiner(" or "); for (AsyncSupport asyncSupport : AsyncSupportRegistry.allKnown()) { - knownAsync.add(asyncSupport.description()); + knownAsync.add(asyncSupport.mustDescription()); } throw new FaultToleranceDefinitionException("Invalid @Asynchronous on " + method() + ": must return java.util.concurrent.Future or " + knownAsync); 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 7bf56a17..782a0180 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 @@ -16,6 +16,7 @@ import io.smallrye.common.annotation.Blocking; import io.smallrye.common.annotation.NonBlocking; +import io.smallrye.faulttolerance.api.ApplyFaultTolerance; import io.smallrye.faulttolerance.api.CircuitBreakerName; import io.smallrye.faulttolerance.api.CustomBackoff; import io.smallrye.faulttolerance.api.ExponentialBackoff; @@ -39,6 +40,7 @@ public static FaultToleranceMethod create(AnnotatedMethod method) { result.retry = getAnnotation(Retry.class, method, annotationsPresentDirectly); result.timeout = getAnnotation(Timeout.class, method, annotationsPresentDirectly); + result.applyFaultTolerance = getAnnotation(ApplyFaultTolerance.class, method, annotationsPresentDirectly); result.circuitBreakerName = getAnnotation(CircuitBreakerName.class, method, annotationsPresentDirectly); result.customBackoff = getAnnotation(CustomBackoff.class, method, annotationsPresentDirectly); result.exponentialBackoff = getAnnotation(ExponentialBackoff.class, method, annotationsPresentDirectly); @@ -85,6 +87,7 @@ public static FaultToleranceMethod create(Class beanClass, Method method) { result.retry = getAnnotation(Retry.class, method, beanClass, annotationsPresentDirectly); result.timeout = getAnnotation(Timeout.class, method, beanClass, annotationsPresentDirectly); + result.applyFaultTolerance = getAnnotation(ApplyFaultTolerance.class, method, beanClass, annotationsPresentDirectly); result.circuitBreakerName = getAnnotation(CircuitBreakerName.class, method, beanClass, annotationsPresentDirectly); result.customBackoff = getAnnotation(CustomBackoff.class, method, beanClass, annotationsPresentDirectly); result.exponentialBackoff = getAnnotation(ExponentialBackoff.class, method, beanClass, annotationsPresentDirectly); 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 7ead4658..ec151f45 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 @@ -28,6 +28,7 @@ import org.eclipse.microprofile.faulttolerance.Timeout; import org.eclipse.microprofile.faulttolerance.exceptions.FaultToleranceDefinitionException; +import io.smallrye.faulttolerance.api.ApplyFaultTolerance; import io.smallrye.faulttolerance.api.CircuitBreakerName; import io.smallrye.faulttolerance.api.CustomBackoff; import io.smallrye.faulttolerance.api.ExponentialBackoff; @@ -45,6 +46,7 @@ public class FaultToleranceOperation { public static FaultToleranceOperation create(FaultToleranceMethod method) { return new FaultToleranceOperation(method.beanClass, method.method, + ApplyFaultToleranceConfigImpl.create(method), AsynchronousConfigImpl.create(method), BlockingConfigImpl.create(method), NonBlockingConfigImpl.create(method), @@ -63,6 +65,8 @@ public static FaultToleranceOperation create(FaultToleranceMethod method) { private final MethodDescriptor methodDescriptor; + private final ApplyFaultToleranceConfig applyFaultTolerance; + private final AsynchronousConfig asynchronous; private final BlockingConfig blocking; @@ -89,6 +93,7 @@ public static FaultToleranceOperation create(FaultToleranceMethod method) { private FaultToleranceOperation(Class beanClass, MethodDescriptor methodDescriptor, + ApplyFaultToleranceConfig applyFaultTolerance, AsynchronousConfig asynchronous, BlockingConfig blocking, NonBlockingConfig nonBlocking, @@ -104,6 +109,8 @@ private FaultToleranceOperation(Class beanClass, this.beanClass = beanClass; this.methodDescriptor = methodDescriptor; + this.applyFaultTolerance = applyFaultTolerance; + this.asynchronous = asynchronous; this.blocking = blocking; this.nonBlocking = nonBlocking; @@ -128,6 +135,14 @@ public Class getReturnType() { return methodDescriptor.returnType; } + public boolean hasApplyFaultTolerance() { + return applyFaultTolerance != null; + } + + public ApplyFaultTolerance getApplyFaultTolerance() { + return applyFaultTolerance; + } + public boolean hasAsynchronous() { return asynchronous != null; } @@ -256,6 +271,10 @@ public boolean isValid() { * Throws {@link FaultToleranceDefinitionException} if validation fails. */ public void validate() { + if (applyFaultTolerance != null) { + applyFaultTolerance.validate(); + } + if (asynchronous != null) { asynchronous.validate(); } diff --git a/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/internal/InterceptionPoint.java b/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/internal/InterceptionPoint.java index f97affc3..e0b35b73 100644 --- a/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/internal/InterceptionPoint.java +++ b/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/internal/InterceptionPoint.java @@ -3,17 +3,15 @@ import java.lang.reflect.Method; import java.util.Objects; -import javax.interceptor.InvocationContext; - public class InterceptionPoint { private final String name; private final Class beanClass; private final Method method; - public InterceptionPoint(Class beanClass, InvocationContext invocationContext) { + public InterceptionPoint(Class beanClass, Method method) { + this.name = beanClass.getName() + "#" + method.getName(); this.beanClass = beanClass; - method = invocationContext.getMethod(); - name = beanClass.getName() + "#" + method.getName(); + this.method = method; } public Class beanClass() { @@ -36,8 +34,8 @@ public boolean equals(Object o) { if (o == null || getClass() != o.getClass()) return false; InterceptionPoint that = (InterceptionPoint) o; - return beanClass.equals(that.beanClass) && - method.equals(that.method); + return beanClass.equals(that.beanClass) + && method.equals(that.method); } @Override diff --git a/implementation/kotlin/src/main/kotlin/io/smallrye/faulttolerance/kotlin/impl/CoroutineSupport.kt b/implementation/kotlin/src/main/kotlin/io/smallrye/faulttolerance/kotlin/impl/CoroutineSupport.kt index e6dddb1e..878aa19a 100644 --- a/implementation/kotlin/src/main/kotlin/io/smallrye/faulttolerance/kotlin/impl/CoroutineSupport.kt +++ b/implementation/kotlin/src/main/kotlin/io/smallrye/faulttolerance/kotlin/impl/CoroutineSupport.kt @@ -17,10 +17,14 @@ import kotlin.coroutines.resumeWithException // the `Any?` type is used here similarly to Kotlin-transformed `suspend` functions: // it stands for a non-denotable union type `T | COROUTINE_SUSPENDED` class CoroutineSupport : AsyncSupport { - override fun description(): String { + override fun mustDescription(): String { return "be a Kotlin suspend function" } + override fun doesDescription(): String { + return "is a Kotlin suspend function" + } + override fun applies(parameterTypes: Array>, returnType: Class<*>): Boolean { return parameterTypes.isNotEmpty() && parameterTypes.last() == Continuation::class.java } diff --git a/implementation/mutiny/src/main/java/io/smallrye/faulttolerance/mutiny/impl/UniSupport.java b/implementation/mutiny/src/main/java/io/smallrye/faulttolerance/mutiny/impl/UniSupport.java index 4b3c6c46..9c51eb26 100644 --- a/implementation/mutiny/src/main/java/io/smallrye/faulttolerance/mutiny/impl/UniSupport.java +++ b/implementation/mutiny/src/main/java/io/smallrye/faulttolerance/mutiny/impl/UniSupport.java @@ -10,10 +10,15 @@ public class UniSupport implements AsyncSupport> { @Override - public String description() { + public String mustDescription() { return "return " + Uni.class.getSimpleName(); } + @Override + public String doesDescription() { + return "returns " + Uni.class.getSimpleName(); + } + @Override public boolean applies(Class[] parameterTypes, Class returnType) { return Uni.class.equals(returnType); diff --git a/implementation/rxjava3/src/main/java/io/smallrye/faulttolerance/rxjava3/impl/CompletableSupport.java b/implementation/rxjava3/src/main/java/io/smallrye/faulttolerance/rxjava3/impl/CompletableSupport.java index 772d0b46..e183bd3f 100644 --- a/implementation/rxjava3/src/main/java/io/smallrye/faulttolerance/rxjava3/impl/CompletableSupport.java +++ b/implementation/rxjava3/src/main/java/io/smallrye/faulttolerance/rxjava3/impl/CompletableSupport.java @@ -8,10 +8,15 @@ public class CompletableSupport implements AsyncSupport { @Override - public String description() { + public String mustDescription() { return "return " + Completable.class.getSimpleName(); } + @Override + public String doesDescription() { + return "returns " + Completable.class.getSimpleName(); + } + @Override public boolean applies(Class[] parameterTypes, Class returnType) { return Completable.class.equals(returnType); diff --git a/implementation/rxjava3/src/main/java/io/smallrye/faulttolerance/rxjava3/impl/MaybeSupport.java b/implementation/rxjava3/src/main/java/io/smallrye/faulttolerance/rxjava3/impl/MaybeSupport.java index 671d8a65..bac81f2f 100644 --- a/implementation/rxjava3/src/main/java/io/smallrye/faulttolerance/rxjava3/impl/MaybeSupport.java +++ b/implementation/rxjava3/src/main/java/io/smallrye/faulttolerance/rxjava3/impl/MaybeSupport.java @@ -8,10 +8,15 @@ public class MaybeSupport implements AsyncSupport> { @Override - public String description() { + public String mustDescription() { return "return " + Maybe.class.getSimpleName(); } + @Override + public String doesDescription() { + return "returns " + Maybe.class.getSimpleName(); + } + @Override public boolean applies(Class[] parameterTypes, Class returnType) { return Maybe.class.equals(returnType); diff --git a/implementation/rxjava3/src/main/java/io/smallrye/faulttolerance/rxjava3/impl/SingleSupport.java b/implementation/rxjava3/src/main/java/io/smallrye/faulttolerance/rxjava3/impl/SingleSupport.java index c3f8fe68..44a53b6e 100644 --- a/implementation/rxjava3/src/main/java/io/smallrye/faulttolerance/rxjava3/impl/SingleSupport.java +++ b/implementation/rxjava3/src/main/java/io/smallrye/faulttolerance/rxjava3/impl/SingleSupport.java @@ -8,10 +8,15 @@ public class SingleSupport implements AsyncSupport> { @Override - public String description() { + public String mustDescription() { return "return " + Single.class.getSimpleName(); } + @Override + public String doesDescription() { + return "returns " + Single.class.getSimpleName(); + } + @Override public boolean applies(Class[] parameterTypes, Class returnType) { return Single.class.equals(returnType); 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 new file mode 100644 index 00000000..8038f3f7 --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/completionstage/MyFaultTolerance.java @@ -0,0 +1,21 @@ +package io.smallrye.faulttolerance.reuse.async.completionstage; + +import static java.util.concurrent.CompletableFuture.completedFuture; + +import java.util.concurrent.CompletionStage; + +import javax.enterprise.context.ApplicationScoped; +import javax.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() + .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 new file mode 100644 index 00000000..de5e0dfd --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/completionstage/MyService.java @@ -0,0 +1,21 @@ +package io.smallrye.faulttolerance.reuse.async.completionstage; + +import static io.smallrye.faulttolerance.core.util.CompletionStages.failedFuture; + +import java.util.concurrent.CompletionStage; +import java.util.concurrent.atomic.AtomicInteger; + +import javax.enterprise.context.ApplicationScoped; + +import io.smallrye.faulttolerance.api.ApplyFaultTolerance; + +@ApplicationScoped +public class MyService { + static final AtomicInteger COUNTER = new AtomicInteger(0); + + @ApplyFaultTolerance("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/ReuseAsyncCompletionStageTest.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/completionstage/ReuseAsyncCompletionStageTest.java new file mode 100644 index 00000000..7ea101cf --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/completionstage/ReuseAsyncCompletionStageTest.java @@ -0,0 +1,19 @@ +package io.smallrye.faulttolerance.reuse.async.completionstage; + +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 ReuseAsyncCompletionStageTest { + @Test + public void test(MyService service) throws ExecutionException, InterruptedException { + assertThat(service.hello().toCompletableFuture().get()).isEqualTo("fallback"); + } +} 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 new file mode 100644 index 00000000..02f67b4d --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/uni/MyFaultTolerance.java @@ -0,0 +1,19 @@ +package io.smallrye.faulttolerance.reuse.async.uni; + +import javax.enterprise.context.ApplicationScoped; +import javax.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() + .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 new file mode 100644 index 00000000..d67972b2 --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/uni/MyService.java @@ -0,0 +1,19 @@ +package io.smallrye.faulttolerance.reuse.async.uni; + +import java.util.concurrent.atomic.AtomicInteger; + +import javax.enterprise.context.ApplicationScoped; + +import io.smallrye.faulttolerance.api.ApplyFaultTolerance; +import io.smallrye.mutiny.Uni; + +@ApplicationScoped +public class MyService { + static final AtomicInteger COUNTER = new AtomicInteger(0); + + @ApplyFaultTolerance("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/ReuseAsyncUniTest.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/uni/ReuseAsyncUniTest.java new file mode 100644 index 00000000..2407a62e --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/uni/ReuseAsyncUniTest.java @@ -0,0 +1,17 @@ +package io.smallrye.faulttolerance.reuse.async.uni; + +import static org.assertj.core.api.Assertions.assertThat; + +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 ReuseAsyncUniTest { + @Test + public void test(MyService service) { + assertThat(service.hello().await().indefinitely()).isEqualTo("fallback"); + } +} 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 new file mode 100644 index 00000000..0da49b86 --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mismatch/async/vs/async/MyFaultTolerance.java @@ -0,0 +1,16 @@ +package io.smallrye.faulttolerance.reuse.mismatch.async.vs.async; + +import javax.enterprise.context.ApplicationScoped; +import javax.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 new file mode 100644 index 00000000..ca697212 --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mismatch/async/vs/async/MyService.java @@ -0,0 +1,15 @@ +package io.smallrye.faulttolerance.reuse.mismatch.async.vs.async; + +import java.util.concurrent.CompletionStage; + +import javax.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 new file mode 100644 index 00000000..0d753b73 --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mismatch/async/vs/async/ReuseMismatchAsyncVsAsyncTest.java @@ -0,0 +1,23 @@ +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 new file mode 100644 index 00000000..c969e21f --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mismatch/async/vs/sync/MyFaultTolerance.java @@ -0,0 +1,16 @@ +package io.smallrye.faulttolerance.reuse.mismatch.async.vs.sync; + +import java.util.concurrent.CompletionStage; + +import javax.enterprise.context.ApplicationScoped; +import javax.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 new file mode 100644 index 00000000..31753030 --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mismatch/async/vs/sync/MyService.java @@ -0,0 +1,13 @@ +package io.smallrye.faulttolerance.reuse.mismatch.async.vs.sync; + +import javax.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 new file mode 100644 index 00000000..832f51df --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mismatch/async/vs/sync/ReuseMismatchAsyncVsSyncTest.java @@ -0,0 +1,21 @@ +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/MyFaultTolerance.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mismatch/sync/vs/async/MyFaultTolerance.java new file mode 100644 index 00000000..83c63c77 --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mismatch/sync/vs/async/MyFaultTolerance.java @@ -0,0 +1,14 @@ +package io.smallrye.faulttolerance.reuse.mismatch.sync.vs.async; + +import javax.enterprise.context.ApplicationScoped; +import javax.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. create().build(); +} 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 new file mode 100644 index 00000000..69b23675 --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mismatch/sync/vs/async/MyService.java @@ -0,0 +1,15 @@ +package io.smallrye.faulttolerance.reuse.mismatch.sync.vs.async; + +import java.util.concurrent.CompletionStage; + +import javax.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 new file mode 100644 index 00000000..df13beb5 --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mismatch/sync/vs/async/ReuseMismatchSyncVsAsyncTest.java @@ -0,0 +1,21 @@ +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 new file mode 100644 index 00000000..135ba7a0 --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/missing/MyService.java @@ -0,0 +1,13 @@ +package io.smallrye.faulttolerance.reuse.missing; + +import javax.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/missing/ReuseMissingTest.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/missing/ReuseMissingTest.java new file mode 100644 index 00000000..966b0686 --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/missing/ReuseMissingTest.java @@ -0,0 +1,19 @@ +package io.smallrye.faulttolerance.reuse.missing; + +import static org.assertj.core.api.Assertions.assertThatCode; + +import org.eclipse.microprofile.faulttolerance.exceptions.FaultToleranceException; +import org.junit.jupiter.api.Test; + +import io.smallrye.faulttolerance.util.FaultToleranceBasicTest; + +@FaultToleranceBasicTest +public class ReuseMissingTest { + @Test + 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\")"); + } +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/async/completionstage/MixedReuseAsyncCompletionStageTest.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/async/completionstage/MixedReuseAsyncCompletionStageTest.java new file mode 100644 index 00000000..9f887e42 --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/async/completionstage/MixedReuseAsyncCompletionStageTest.java @@ -0,0 +1,23 @@ +package io.smallrye.faulttolerance.reuse.mixed.async.completionstage; + +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 MixedReuseAsyncCompletionStageTest { + @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().toCompletableFuture().get()).isEqualTo(42); + assertThat(MyService.INT_COUNTER).hasValue(4); + } +} 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 new file mode 100644 index 00000000..bded3f24 --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/async/completionstage/MyFaultTolerance.java @@ -0,0 +1,19 @@ +package io.smallrye.faulttolerance.reuse.mixed.async.completionstage; + +import java.util.concurrent.CompletionStage; + +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.inject.Produces; + +import io.smallrye.common.annotation.Identifier; +import io.smallrye.faulttolerance.api.FaultTolerance; + +@ApplicationScoped +public class MyFaultTolerance { + // can't define fallback, that's intrinsically typed + @Produces + @Identifier("my-fault-tolerance") + public static final FaultTolerance> FT = FaultTolerance.createAsync() + .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 new file mode 100644 index 00000000..df51456f --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/async/completionstage/MyService.java @@ -0,0 +1,33 @@ +package io.smallrye.faulttolerance.reuse.mixed.async.completionstage; + +import static io.smallrye.faulttolerance.core.util.CompletionStages.failedFuture; +import static java.util.concurrent.CompletableFuture.completedFuture; + +import java.util.concurrent.CompletionStage; +import java.util.concurrent.atomic.AtomicInteger; + +import javax.enterprise.context.ApplicationScoped; + +import io.smallrye.faulttolerance.api.ApplyFaultTolerance; + +@ApplicationScoped +public class MyService { + static final AtomicInteger STRING_COUNTER = new AtomicInteger(0); + static final AtomicInteger INT_COUNTER = new AtomicInteger(0); + + @ApplyFaultTolerance("my-fault-tolerance") + public CompletionStage hello() { + if (STRING_COUNTER.incrementAndGet() > 3) { + return completedFuture("hello"); + } + return failedFuture(new IllegalArgumentException()); + } + + @ApplyFaultTolerance("my-fault-tolerance") + public CompletionStage theAnswer() { + if (INT_COUNTER.incrementAndGet() > 3) { + return completedFuture(42); + } + return failedFuture(new IllegalArgumentException()); + } +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/async/uni/MixedReuseAsyncUniTest.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/async/uni/MixedReuseAsyncUniTest.java new file mode 100644 index 00000000..9b986d0b --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/async/uni/MixedReuseAsyncUniTest.java @@ -0,0 +1,23 @@ +package io.smallrye.faulttolerance.reuse.mixed.async.uni; + +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 MixedReuseAsyncUniTest { + @Test + public void test(MyService service) throws ExecutionException, InterruptedException { + assertThat(service.hello().await().indefinitely()).isEqualTo("hello"); + assertThat(MyService.STRING_COUNTER).hasValue(4); + + assertThat(service.theAnswer().await().indefinitely()).isEqualTo(42); + assertThat(MyService.INT_COUNTER).hasValue(4); + } +} 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 new file mode 100644 index 00000000..097cbc29 --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/async/uni/MyFaultTolerance.java @@ -0,0 +1,19 @@ +package io.smallrye.faulttolerance.reuse.mixed.async.uni; + +import javax.enterprise.context.ApplicationScoped; +import javax.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 { + // can't define fallback, that's intrinsically typed + @Produces + @Identifier("my-fault-tolerance") + public static final FaultTolerance> FT = MutinyFaultTolerance.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 new file mode 100644 index 00000000..27c56a0e --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/async/uni/MyService.java @@ -0,0 +1,30 @@ +package io.smallrye.faulttolerance.reuse.mixed.async.uni; + +import java.util.concurrent.atomic.AtomicInteger; + +import javax.enterprise.context.ApplicationScoped; + +import io.smallrye.faulttolerance.api.ApplyFaultTolerance; +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); + + @ApplyFaultTolerance("my-fault-tolerance") + public Uni hello() { + if (STRING_COUNTER.incrementAndGet() > 3) { + return Uni.createFrom().item("hello"); + } + return Uni.createFrom().failure(new IllegalArgumentException()); + } + + @ApplyFaultTolerance("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/sync/MixedReuseSyncTest.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/sync/MixedReuseSyncTest.java new file mode 100644 index 00000000..4f54c1af --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/sync/MixedReuseSyncTest.java @@ -0,0 +1,21 @@ +package io.smallrye.faulttolerance.reuse.mixed.sync; + +import static org.assertj.core.api.Assertions.assertThat; + +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 MixedReuseSyncTest { + @Test + public void test(MyService service) { + assertThat(service.hello()).isEqualTo("hello"); + assertThat(MyService.STRING_COUNTER).hasValue(4); + + assertThat(service.theAnswer()).isEqualTo(42); + assertThat(MyService.INT_COUNTER).hasValue(4); + } +} 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 new file mode 100644 index 00000000..a3794edb --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/sync/MyFaultTolerance.java @@ -0,0 +1,17 @@ +package io.smallrye.faulttolerance.reuse.mixed.sync; + +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.inject.Produces; + +import io.smallrye.common.annotation.Identifier; +import io.smallrye.faulttolerance.api.FaultTolerance; + +@ApplicationScoped +public class MyFaultTolerance { + // can't define fallback, that's intrinsically typed + @Produces + @Identifier("my-fault-tolerance") + public static final FaultTolerance FT = FaultTolerance.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 new file mode 100644 index 00000000..0a4a9077 --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/sync/MyService.java @@ -0,0 +1,29 @@ +package io.smallrye.faulttolerance.reuse.mixed.sync; + +import java.util.concurrent.atomic.AtomicInteger; + +import javax.enterprise.context.ApplicationScoped; + +import io.smallrye.faulttolerance.api.ApplyFaultTolerance; + +@ApplicationScoped +public class MyService { + static final AtomicInteger STRING_COUNTER = new AtomicInteger(0); + static final AtomicInteger INT_COUNTER = new AtomicInteger(0); + + @ApplyFaultTolerance("my-fault-tolerance") + public String hello() { + if (STRING_COUNTER.incrementAndGet() > 3) { + return "hello"; + } + throw new IllegalArgumentException(); + } + + @ApplyFaultTolerance("my-fault-tolerance") + public int theAnswer() { + if (INT_COUNTER.incrementAndGet() > 3) { + return 42; + } + throw new IllegalArgumentException(); + } +} 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 new file mode 100644 index 00000000..4abf01ad --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/sync/MyFaultTolerance.java @@ -0,0 +1,17 @@ +package io.smallrye.faulttolerance.reuse.sync; + +import javax.enterprise.context.ApplicationScoped; +import javax.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. create() + .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 new file mode 100644 index 00000000..c48edca6 --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/sync/MyService.java @@ -0,0 +1,18 @@ +package io.smallrye.faulttolerance.reuse.sync; + +import java.util.concurrent.atomic.AtomicInteger; + +import javax.enterprise.context.ApplicationScoped; + +import io.smallrye.faulttolerance.api.ApplyFaultTolerance; + +@ApplicationScoped +public class MyService { + static final AtomicInteger COUNTER = new AtomicInteger(0); + + @ApplyFaultTolerance("my-fault-tolerance") + public String hello() { + COUNTER.incrementAndGet(); + throw new IllegalArgumentException(); + } +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/sync/ReuseSyncTest.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/sync/ReuseSyncTest.java new file mode 100644 index 00000000..17fe0155 --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/sync/ReuseSyncTest.java @@ -0,0 +1,17 @@ +package io.smallrye.faulttolerance.reuse.sync; + +import static org.assertj.core.api.Assertions.assertThat; + +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 ReuseSyncTest { + @Test + public void test(MyService service) { + assertThat(service.hello()).isEqualTo("fallback"); + } +} 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 new file mode 100644 index 00000000..f21e7e51 --- /dev/null +++ b/testsuite/basic/src/test/kotlin/io/smallrye/faulttolerance/kotlin/reuse/MyFaultTolerance.kt @@ -0,0 +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 javax.enterprise.context.ApplicationScoped +import javax.enterprise.inject.Produces + +@ApplicationScoped +object MyFaultTolerance { + @Produces + @Identifier("my-fault-tolerance") + val FT = FaultTolerance.create() + .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 new file mode 100644 index 00000000..e4d13950 --- /dev/null +++ b/testsuite/basic/src/test/kotlin/io/smallrye/faulttolerance/kotlin/reuse/MyService.kt @@ -0,0 +1,19 @@ +package io.smallrye.faulttolerance.kotlin.reuse + +import io.smallrye.faulttolerance.api.ApplyFaultTolerance +import java.lang.IllegalArgumentException +import java.util.concurrent.atomic.AtomicInteger +import javax.enterprise.context.ApplicationScoped + +@ApplicationScoped +open class MyService { + companion object { + val COUNTER = AtomicInteger(0) + } + + @ApplyFaultTolerance("my-fault-tolerance") + open fun hello(): String { + COUNTER.incrementAndGet() + throw IllegalArgumentException() + } +} diff --git a/testsuite/basic/src/test/kotlin/io/smallrye/faulttolerance/kotlin/reuse/ReuseKotlinTest.kt b/testsuite/basic/src/test/kotlin/io/smallrye/faulttolerance/kotlin/reuse/ReuseKotlinTest.kt new file mode 100644 index 00000000..74160ec5 --- /dev/null +++ b/testsuite/basic/src/test/kotlin/io/smallrye/faulttolerance/kotlin/reuse/ReuseKotlinTest.kt @@ -0,0 +1,15 @@ +package io.smallrye.faulttolerance.kotlin.reuse + +import io.smallrye.faulttolerance.util.FaultToleranceBasicTest +import org.assertj.core.api.Assertions.assertThat +import org.jboss.weld.junit5.auto.AddBeanClasses +import org.junit.jupiter.api.Test + +@FaultToleranceBasicTest +@AddBeanClasses(MyFaultTolerance::class) +class ReuseKotlinTest { + @Test + fun test(service: MyService) { + assertThat(service.hello()).isEqualTo("fallback") + } +} From 152b301c1badf8654bce79f7a734847eded3ebf2 Mon Sep 17 00:00:00 2001 From: Ladislav Thon Date: Wed, 30 Mar 2022 17:53:59 +0200 Subject: [PATCH 60/68] instantiate programmatically created FaultTolerance lazily --- .../apiimpl/BuilderEagerDependencies.java | 6 + .../core/apiimpl/BuilderLazyDependencies.java | 17 +++ .../core/apiimpl/FaultToleranceImpl.java | 119 ++++++++---------- .../core/apiimpl/LazyFaultTolerance.java | 35 ++++++ .../faulttolerance/CdiFaultToleranceSpi.java | 53 +++++--- .../FaultToleranceExtension.java | 6 +- .../FaultToleranceInterceptor.java | 8 +- .../StandaloneFaultToleranceSpi.java | 59 ++++++--- 8 files changed, 201 insertions(+), 102 deletions(-) create mode 100644 implementation/core/src/main/java/io/smallrye/faulttolerance/core/apiimpl/BuilderEagerDependencies.java create mode 100644 implementation/core/src/main/java/io/smallrye/faulttolerance/core/apiimpl/BuilderLazyDependencies.java create mode 100644 implementation/core/src/main/java/io/smallrye/faulttolerance/core/apiimpl/LazyFaultTolerance.java diff --git a/implementation/core/src/main/java/io/smallrye/faulttolerance/core/apiimpl/BuilderEagerDependencies.java b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/apiimpl/BuilderEagerDependencies.java new file mode 100644 index 00000000..fa023d88 --- /dev/null +++ b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/apiimpl/BuilderEagerDependencies.java @@ -0,0 +1,6 @@ +package io.smallrye.faulttolerance.core.apiimpl; + +// dependencies that may be accessed eagerly; these must be safe to use during static initialization +public interface BuilderEagerDependencies { + BasicCircuitBreakerMaintenanceImpl cbMaintenance(); +} diff --git a/implementation/core/src/main/java/io/smallrye/faulttolerance/core/apiimpl/BuilderLazyDependencies.java b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/apiimpl/BuilderLazyDependencies.java new file mode 100644 index 00000000..26fabaee --- /dev/null +++ b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/apiimpl/BuilderLazyDependencies.java @@ -0,0 +1,17 @@ +package io.smallrye.faulttolerance.core.apiimpl; + +import java.util.concurrent.ExecutorService; + +import io.smallrye.faulttolerance.core.event.loop.EventLoop; +import io.smallrye.faulttolerance.core.timer.Timer; + +// dependencies that must NOT be accessed eagerly; these are NOT safe to use during static initialization +public interface BuilderLazyDependencies { + boolean ftEnabled(); + + ExecutorService asyncExecutor(); + + EventLoop eventLoop(); + + Timer timer(); +} 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 bf63b214..e2e72e3f 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 @@ -4,10 +4,8 @@ import java.time.Duration; import java.time.temporal.ChronoUnit; -import java.util.ArrayList; import java.util.Collection; import java.util.Collections; -import java.util.List; import java.util.UUID; import java.util.concurrent.Callable; import java.util.concurrent.CompletionStage; @@ -31,7 +29,6 @@ import io.smallrye.faulttolerance.core.circuit.breaker.CircuitBreaker; import io.smallrye.faulttolerance.core.circuit.breaker.CircuitBreakerEvents; import io.smallrye.faulttolerance.core.circuit.breaker.CompletionStageCircuitBreaker; -import io.smallrye.faulttolerance.core.event.loop.EventLoop; import io.smallrye.faulttolerance.core.fallback.CompletionStageFallback; import io.smallrye.faulttolerance.core.fallback.Fallback; import io.smallrye.faulttolerance.core.fallback.FallbackFunction; @@ -54,10 +51,8 @@ import io.smallrye.faulttolerance.core.timeout.CompletionStageTimeout; import io.smallrye.faulttolerance.core.timeout.Timeout; import io.smallrye.faulttolerance.core.timeout.TimerTimeoutWatcher; -import io.smallrye.faulttolerance.core.timer.Timer; import io.smallrye.faulttolerance.core.util.DirectExecutor; import io.smallrye.faulttolerance.core.util.ExceptionDecision; -import io.smallrye.faulttolerance.core.util.Initializer; import io.smallrye.faulttolerance.core.util.Preconditions; import io.smallrye.faulttolerance.core.util.PredicateBasedExceptionDecision; import io.smallrye.faulttolerance.core.util.SetBasedExceptionDecision; @@ -73,7 +68,6 @@ public final class FaultToleranceImpl implements FaultTolerance { private final FaultToleranceStrategy strategy; private final AsyncSupport asyncSupport; private final EventHandlers eventHandlers; - private final Initializer initializer; // Circuit breakers created using the programmatic API are registered with `CircuitBreakerMaintenance` // in two phases: @@ -93,22 +87,14 @@ public final class FaultToleranceImpl implements FaultTolerance { // but that instance is never used. The useful `FaultTolerance` instance is held by the actual bean instance, // which is created lazily, on the first method invocation on the client proxy. - FaultToleranceImpl(FaultToleranceStrategy strategy, AsyncSupport asyncSupport, EventHandlers eventHandlers, - Initializer initializer) { + FaultToleranceImpl(FaultToleranceStrategy strategy, AsyncSupport asyncSupport, EventHandlers eventHandlers) { this.strategy = strategy; this.asyncSupport = asyncSupport; this.eventHandlers = eventHandlers; - this.initializer = initializer; - } - - public AsyncSupport internalGetAsyncSupport() { - return asyncSupport; } @Override public T call(Callable action) throws Exception { - initializer.runOnce(); - if (asyncSupport == null) { InvocationContext ctx = new InvocationContext<>(action); eventHandlers.register(ctx); @@ -124,13 +110,10 @@ public T call(Callable action) throws Exception { } public static final class BuilderImpl implements Builder { - private final boolean ftEnabled; - private final Executor executor; - private final Timer timer; - private final EventLoop eventLoop; - private final BasicCircuitBreakerMaintenanceImpl cbMaintenance; + private final BuilderEagerDependencies eagerDependencies; + private final Supplier lazyDependencies; private final boolean isAsync; - private final Class asyncType; // ignored when isAsync == false + private final Class asyncType; // null when isAsync == false private final Function, R> finisher; private String description; @@ -141,15 +124,11 @@ public static final class BuilderImpl implements Builder { private TimeoutBuilderImpl timeoutBuilder; private boolean offloadToAnotherThread; - public BuilderImpl(boolean ftEnabled, Executor executor, Timer timer, EventLoop eventLoop, - BasicCircuitBreakerMaintenanceImpl cbMaintenance, boolean isAsync, Class asyncType, - Function, R> finisher) { - this.ftEnabled = ftEnabled; - this.executor = executor; - this.timer = timer; - this.eventLoop = eventLoop; - this.cbMaintenance = cbMaintenance; - this.isAsync = isAsync; + public BuilderImpl(BuilderEagerDependencies eagerDependencies, Supplier lazyDependencies, + Class asyncType, Function, R> finisher) { + this.eagerDependencies = eagerDependencies; + this.lazyDependencies = lazyDependencies; + this.isAsync = asyncType != null; this.asyncType = asyncType; this.finisher = finisher; @@ -199,9 +178,24 @@ public Builder withThreadOffload(boolean value) { @Override public R build() { + eagerInitialization(); + + FaultTolerance faultTolerance = new LazyFaultTolerance<>(() -> build(lazyDependencies.get()), asyncType); + return finisher.apply(faultTolerance); + } + + // must not access lazyDependencies + private void eagerInitialization() { + if (circuitBreakerBuilder != null && circuitBreakerBuilder.name != null) { + eagerDependencies.cbMaintenance().registerName(circuitBreakerBuilder.name); + } + } + + private FaultTolerance build(BuilderLazyDependencies lazyDependencies) { Consumer cbMaintenanceEventHandler = null; if (circuitBreakerBuilder != null && circuitBreakerBuilder.name != null) { - cbMaintenanceEventHandler = cbMaintenance.stateTransitionEventHandler(circuitBreakerBuilder.name); + cbMaintenanceEventHandler = eagerDependencies.cbMaintenance() + .stateTransitionEventHandler(circuitBreakerBuilder.name); } EventHandlers eventHandlers = new EventHandlers( bulkheadBuilder != null ? bulkheadBuilder.onAccepted : null, @@ -218,39 +212,33 @@ public R build() { timeoutBuilder != null ? timeoutBuilder.onTimeout : null, timeoutBuilder != null ? timeoutBuilder.onFinished : null); - return isAsync ? buildAsync(eventHandlers) : buildSync(eventHandlers); + return isAsync ? buildAsync(lazyDependencies, eventHandlers) : buildSync(lazyDependencies, eventHandlers); } - private R buildSync(EventHandlers eventHandlers) { - List initActions = new ArrayList<>(); - FaultToleranceStrategy strategy = buildSyncStrategy(initActions); - FaultTolerance result = new FaultToleranceImpl<>(strategy, (AsyncSupport) null, eventHandlers, - new Initializer(initActions)); - return finisher.apply(result); + private FaultTolerance buildSync(BuilderLazyDependencies lazyDependencies, EventHandlers eventHandlers) { + FaultToleranceStrategy strategy = buildSyncStrategy(lazyDependencies); + return new FaultToleranceImpl<>(strategy, (AsyncSupport) null, eventHandlers); } - private R buildAsync(EventHandlers eventHandlers) { - List initActions = new ArrayList<>(); - FaultToleranceStrategy> strategy = buildAsyncStrategy(initActions); + private FaultTolerance buildAsync(BuilderLazyDependencies lazyDependencies, EventHandlers eventHandlers) { + FaultToleranceStrategy> strategy = buildAsyncStrategy(lazyDependencies); AsyncSupport asyncSupport = AsyncSupportRegistry.get(new Class[0], asyncType); - FaultTolerance result = new FaultToleranceImpl<>(strategy, asyncSupport, eventHandlers, - new Initializer(initActions)); - return finisher.apply(result); + return new FaultToleranceImpl<>(strategy, asyncSupport, eventHandlers); } - private FaultToleranceStrategy buildSyncStrategy(List initActions) { + private FaultToleranceStrategy buildSyncStrategy(BuilderLazyDependencies lazyDependencies) { FaultToleranceStrategy result = invocation(); - if (ftEnabled && bulkheadBuilder != null) { + if (lazyDependencies.ftEnabled() && bulkheadBuilder != null) { result = new SemaphoreBulkhead<>(result, description, bulkheadBuilder.limit); } - if (ftEnabled && timeoutBuilder != null) { + if (lazyDependencies.ftEnabled() && timeoutBuilder != null) { result = new Timeout<>(result, description, timeoutBuilder.durationInMillis, - new TimerTimeoutWatcher(timer)); + new TimerTimeoutWatcher(lazyDependencies.timer())); } - if (ftEnabled && circuitBreakerBuilder != null) { + if (lazyDependencies.ftEnabled() && circuitBreakerBuilder != null) { result = new CircuitBreaker<>(result, description, createExceptionDecision(circuitBreakerBuilder.skipOn, circuitBreakerBuilder.failOn, circuitBreakerBuilder.whenPredicate), @@ -261,16 +249,12 @@ private FaultToleranceStrategy buildSyncStrategy(List initActions) new SystemStopwatch()); if (circuitBreakerBuilder.name != null) { - cbMaintenance.registerName(circuitBreakerBuilder.name); - CircuitBreaker circuitBreaker = (CircuitBreaker) result; - initActions.add(() -> { - cbMaintenance.register(circuitBreakerBuilder.name, circuitBreaker); - }); + eagerDependencies.cbMaintenance().register(circuitBreakerBuilder.name, circuitBreaker); } } - if (ftEnabled && retryBuilder != null) { + if (lazyDependencies.ftEnabled() && retryBuilder != null) { Supplier backoff = prepareRetryBackoff(retryBuilder); result = new Retry<>(result, description, @@ -290,24 +274,24 @@ private FaultToleranceStrategy buildSyncStrategy(List initActions) return result; } - private FaultToleranceStrategy> buildAsyncStrategy(List initActions) { + private FaultToleranceStrategy> buildAsyncStrategy(BuilderLazyDependencies lazyDependencies) { FaultToleranceStrategy> result = invocation(); // thread offload is always enabled - Executor executor = offloadToAnotherThread ? this.executor : DirectExecutor.INSTANCE; + Executor executor = offloadToAnotherThread ? lazyDependencies.asyncExecutor() : DirectExecutor.INSTANCE; result = new CompletionStageExecution<>(result, executor); - if (ftEnabled && bulkheadBuilder != null) { + if (lazyDependencies.ftEnabled() && bulkheadBuilder != null) { result = new CompletionStageThreadPoolBulkhead<>(result, description, bulkheadBuilder.limit, bulkheadBuilder.queueSize); } - if (ftEnabled && timeoutBuilder != null) { + if (lazyDependencies.ftEnabled() && timeoutBuilder != null) { result = new CompletionStageTimeout<>(result, description, timeoutBuilder.durationInMillis, - new TimerTimeoutWatcher(timer)); + new TimerTimeoutWatcher(lazyDependencies.timer())); } - if (ftEnabled && circuitBreakerBuilder != null) { + if (lazyDependencies.ftEnabled() && circuitBreakerBuilder != null) { result = new CompletionStageCircuitBreaker<>(result, description, createExceptionDecision(circuitBreakerBuilder.skipOn, circuitBreakerBuilder.failOn, circuitBreakerBuilder.whenPredicate), @@ -318,21 +302,18 @@ private FaultToleranceStrategy> buildAsyncStrategy(List circuitBreaker = (CircuitBreaker) result; - initActions.add(() -> { - cbMaintenance.register(circuitBreakerBuilder.name, circuitBreaker); - }); + eagerDependencies.cbMaintenance().register(circuitBreakerBuilder.name, circuitBreaker); } } - if (ftEnabled && retryBuilder != null) { + if (lazyDependencies.ftEnabled() && retryBuilder != null) { Supplier backoff = prepareRetryBackoff(retryBuilder); result = new CompletionStageRetry<>(result, description, createExceptionDecision(retryBuilder.abortOn, retryBuilder.retryOn, retryBuilder.whenPredicate), - retryBuilder.maxRetries, retryBuilder.maxDurationInMillis, () -> new TimerDelay(backoff.get(), timer), + retryBuilder.maxRetries, retryBuilder.maxDurationInMillis, + () -> new TimerDelay(backoff.get(), lazyDependencies.timer()), new SystemStopwatch()); } @@ -353,7 +334,7 @@ private FaultToleranceStrategy> buildAsyncStrategy(List(result, eventLoop); + result = new RememberEventLoop<>(result, lazyDependencies.eventLoop()); } return result; diff --git a/implementation/core/src/main/java/io/smallrye/faulttolerance/core/apiimpl/LazyFaultTolerance.java b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/apiimpl/LazyFaultTolerance.java new file mode 100644 index 00000000..ade0430d --- /dev/null +++ b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/apiimpl/LazyFaultTolerance.java @@ -0,0 +1,35 @@ +package io.smallrye.faulttolerance.core.apiimpl; + +import java.util.concurrent.Callable; +import java.util.function.Supplier; + +import io.smallrye.faulttolerance.api.FaultTolerance; + +public final class LazyFaultTolerance implements FaultTolerance { + private final Supplier> builder; + private final Class asyncType; + + private volatile FaultTolerance instance; + + LazyFaultTolerance(Supplier> builder, Class asyncType) { + this.builder = builder; + this.asyncType = asyncType; + } + + public Class internalGetAsyncType() { + return asyncType; + } + + @Override + public T call(Callable action) throws Exception { + if (instance == null) { + synchronized (this) { + if (instance == null) { + instance = builder.get(); + } + } + } + + return instance.call(action); + } +} diff --git a/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/CdiFaultToleranceSpi.java b/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/CdiFaultToleranceSpi.java index 19796523..7e800f11 100644 --- a/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/CdiFaultToleranceSpi.java +++ b/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/CdiFaultToleranceSpi.java @@ -12,13 +12,27 @@ import io.smallrye.faulttolerance.api.CircuitBreakerMaintenance; import io.smallrye.faulttolerance.api.FaultTolerance; import io.smallrye.faulttolerance.api.FaultToleranceSpi; +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.event.loop.EventLoop; import io.smallrye.faulttolerance.core.timer.Timer; public class CdiFaultToleranceSpi implements FaultToleranceSpi { @Singleton - public static class Dependencies { + public static class EagerDependencies implements BuilderEagerDependencies { + @Inject + CircuitBreakerMaintenanceImpl cbMaintenance; + + @Override + public BasicCircuitBreakerMaintenanceImpl cbMaintenance() { + return cbMaintenance; + } + } + + @Singleton + public static class LazyDependencies implements BuilderLazyDependencies { @Inject @ConfigProperty(name = "MP_Fault_Tolerance_NonFallback_Enabled", defaultValue = "true") boolean ftEnabled; @@ -29,29 +43,44 @@ public static class Dependencies { @Inject CircuitBreakerMaintenanceImpl cbMaintenance; - ExecutorService asyncExecutor() { + @Override + public boolean ftEnabled() { + return ftEnabled; + } + + @Override + public ExecutorService asyncExecutor() { return executorHolder.getAsyncExecutor(); } - EventLoop eventLoop() { + @Override + public EventLoop eventLoop() { return executorHolder.getEventLoop(); } - Timer timer() { + @Override + public Timer timer() { return executorHolder.getTimer(); } } - private Dependencies getDependencies() { + private BuilderEagerDependencies eagerDependencies() { + // always lookup from current CDI container, because there's no guarantee that `CDI.current()` will always be + // the same (in certain environments, e.g. in the test suite, CDI containers come and go freely) + return CDI.current().select(EagerDependencies.class).get(); + } + + private BuilderLazyDependencies lazyDependencies() { // always lookup from current CDI container, because there's no guarantee that `CDI.current()` will always be // the same (in certain environments, e.g. in the test suite, CDI containers come and go freely) - return CDI.current().select(Dependencies.class).get(); + return CDI.current().select(LazyDependencies.class).get(); } @Override public boolean applies() { try { - return getDependencies() != null; + CDI.current(); + return true; } catch (Exception ignored) { return false; } @@ -64,20 +93,16 @@ public int priority() { @Override public FaultTolerance.Builder newBuilder(Function, R> finisher) { - Dependencies deps = getDependencies(); - return new FaultToleranceImpl.BuilderImpl<>(deps.ftEnabled, deps.asyncExecutor(), deps.timer(), deps.eventLoop(), - deps.cbMaintenance, false, null, finisher); + return new FaultToleranceImpl.BuilderImpl<>(eagerDependencies(), this::lazyDependencies, null, finisher); } @Override public FaultTolerance.Builder newAsyncBuilder(Class asyncType, Function, R> finisher) { - Dependencies deps = getDependencies(); - return new FaultToleranceImpl.BuilderImpl<>(deps.ftEnabled, deps.asyncExecutor(), deps.timer(), deps.eventLoop(), - deps.cbMaintenance, true, asyncType, finisher); + return new FaultToleranceImpl.BuilderImpl<>(eagerDependencies(), this::lazyDependencies, asyncType, finisher); } @Override public CircuitBreakerMaintenance circuitBreakerMaintenance() { - return getDependencies().cbMaintenance; + return eagerDependencies().cbMaintenance(); } } 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 3ebd01e3..85d06ae6 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 @@ -119,8 +119,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.Dependencies.class), - CdiFaultToleranceSpi.Dependencies.class.getName()); + bbd.addAnnotatedType(bm.createAnnotatedType(CdiFaultToleranceSpi.EagerDependencies.class), + CdiFaultToleranceSpi.EagerDependencies.class.getName()); + bbd.addAnnotatedType(bm.createAnnotatedType(CdiFaultToleranceSpi.LazyDependencies.class), + CdiFaultToleranceSpi.LazyDependencies.class.getName()); } void changeInterceptorPriority(@Observes ProcessAnnotatedType event) { 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 6d28ed13..3ca0879c 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 @@ -52,7 +52,7 @@ import io.smallrye.faulttolerance.api.FaultTolerance; import io.smallrye.faulttolerance.config.FaultToleranceOperation; import io.smallrye.faulttolerance.core.FaultToleranceStrategy; -import io.smallrye.faulttolerance.core.apiimpl.FaultToleranceImpl; +import io.smallrye.faulttolerance.core.apiimpl.LazyFaultTolerance; import io.smallrye.faulttolerance.core.async.CompletionStageExecution; import io.smallrye.faulttolerance.core.async.FutureExecution; import io.smallrye.faulttolerance.core.async.RememberEventLoop; @@ -192,13 +192,15 @@ private Object preconfiguredFlow(FaultToleranceOperation operation, InvocationCo + " with qualifier @" + Identifier.class.getName() + "(\"" + identifier + "\")"); } FaultTolerance faultTolerance = (FaultTolerance) instance.get(); - if (!(faultTolerance instanceof FaultToleranceImpl)) { + if (!(faultTolerance instanceof LazyFaultTolerance)) { throw new FaultToleranceException("Configured fault tolerance '" + identifier + "' is not created by the FaultTolerance API, this is not supported"); } + Class asyncType = ((LazyFaultTolerance) faultTolerance).internalGetAsyncType(); + AsyncSupport forOperation = AsyncSupportRegistry.get(operation.getParameterTypes(), operation.getReturnType()); - AsyncSupport fromConfigured = ((FaultToleranceImpl) faultTolerance).internalGetAsyncSupport(); + AsyncSupport fromConfigured = asyncType == null ? null : AsyncSupportRegistry.get(new Class[0], asyncType); if (forOperation == null && fromConfigured == null) { return faultTolerance.call(interceptionContext::proceed); diff --git a/implementation/standalone/src/main/java/io/smallrye/faulttolerance/standalone/StandaloneFaultToleranceSpi.java b/implementation/standalone/src/main/java/io/smallrye/faulttolerance/standalone/StandaloneFaultToleranceSpi.java index b9250f07..2c75aacf 100644 --- a/implementation/standalone/src/main/java/io/smallrye/faulttolerance/standalone/StandaloneFaultToleranceSpi.java +++ b/implementation/standalone/src/main/java/io/smallrye/faulttolerance/standalone/StandaloneFaultToleranceSpi.java @@ -1,6 +1,6 @@ package io.smallrye.faulttolerance.standalone; -import java.util.concurrent.Executor; +import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.function.Function; @@ -8,24 +8,57 @@ import io.smallrye.faulttolerance.api.FaultTolerance; import io.smallrye.faulttolerance.api.FaultToleranceSpi; 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.event.loop.EventLoop; import io.smallrye.faulttolerance.core.timer.Timer; public class StandaloneFaultToleranceSpi implements FaultToleranceSpi { - static class Dependencies { + static class EagerDependencies implements BuilderEagerDependencies { + final BasicCircuitBreakerMaintenanceImpl cbMaintenance = new BasicCircuitBreakerMaintenanceImpl(); + + @Override + public BasicCircuitBreakerMaintenanceImpl cbMaintenance() { + return cbMaintenance; + } + } + + static class LazyDependencies implements BuilderLazyDependencies { boolean ftEnabled = !"false".equals(System.getProperty("MP_Fault_Tolerance_NonFallback_Enabled")); // TODO let users integrate their own thread pool - final Executor executor = Executors.newCachedThreadPool(); - final Timer timer = new Timer(executor); + final ExecutorService executor = Executors.newCachedThreadPool(); final EventLoop eventLoop = EventLoop.get(); + final Timer timer = new Timer(executor); - final BasicCircuitBreakerMaintenanceImpl cbMaintenance = new BasicCircuitBreakerMaintenanceImpl(); + @Override + public boolean ftEnabled() { + return ftEnabled; + } + + @Override + public ExecutorService asyncExecutor() { + return executor; + } + + @Override + public EventLoop eventLoop() { + return eventLoop; + } + + @Override + public Timer timer() { + return timer; + } + } + + static class EagerDependenciesHolder { + static final EagerDependencies INSTANCE = new EagerDependencies(); } - static class DependenciesHolder { - static final Dependencies INSTANCE = new Dependencies(); + static class LazyDependenciesHolder { + static final LazyDependencies INSTANCE = new LazyDependencies(); } @Override @@ -40,20 +73,18 @@ public int priority() { @Override public FaultTolerance.Builder newBuilder(Function, R> finisher) { - Dependencies deps = DependenciesHolder.INSTANCE; - return new FaultToleranceImpl.BuilderImpl<>(deps.ftEnabled, deps.executor, deps.timer, deps.eventLoop, - deps.cbMaintenance, false, null, finisher); + return new FaultToleranceImpl.BuilderImpl<>(EagerDependenciesHolder.INSTANCE, () -> LazyDependenciesHolder.INSTANCE, + null, finisher); } @Override public FaultTolerance.Builder newAsyncBuilder(Class asyncType, Function, R> finisher) { - Dependencies deps = DependenciesHolder.INSTANCE; - return new FaultToleranceImpl.BuilderImpl<>(deps.ftEnabled, deps.executor, deps.timer, deps.eventLoop, - deps.cbMaintenance, true, asyncType, finisher); + return new FaultToleranceImpl.BuilderImpl<>(EagerDependenciesHolder.INSTANCE, () -> LazyDependenciesHolder.INSTANCE, + asyncType, finisher); } @Override public CircuitBreakerMaintenance circuitBreakerMaintenance() { - return DependenciesHolder.INSTANCE.cbMaintenance; + return EagerDependenciesHolder.INSTANCE.cbMaintenance; } } From 283a35d387f002f2c8d48197f33ad102b24532d2 Mon Sep 17 00:00:00 2001 From: Ladislav Thon Date: Thu, 31 Mar 2022 12:41:16 +0200 Subject: [PATCH 61/68] release 5.4.0 --- .github/project.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/project.yml b/.github/project.yml index 28d276fe..2ebdba9c 100644 --- a/.github/project.yml +++ b/.github/project.yml @@ -1,4 +1,4 @@ name: SmallRye Fault Tolerance release: - current-version: 5.3.2 - next-version: 5.3.3-SNAPSHOT + current-version: 5.4.0 + next-version: 5.4.1-SNAPSHOT From 01d9b60aab1cd91760e485e5ee86dfa430b1291f Mon Sep 17 00:00:00 2001 From: Ladislav Thon Date: Thu, 31 Mar 2022 13:22:17 +0200 Subject: [PATCH 62/68] fix javadoc --- .../api/ApplyFaultTolerance.java | 41 +++---------------- 1 file changed, 5 insertions(+), 36 deletions(-) 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 f05fb312..9f55118b 100644 --- a/api/src/main/java/io/smallrye/faulttolerance/api/ApplyFaultTolerance.java +++ b/api/src/main/java/io/smallrye/faulttolerance/api/ApplyFaultTolerance.java @@ -19,48 +19,17 @@ * 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). *

- * 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. - *

- * * It is customary to create such bean by declaring a {@code static} producer field. - * For example: - * - * - *

{@code
- * @ApplicationScoped
- * public class PreconfiguredFaultTolerance {
- *     @Produces
- *     @Identifier("my-fault-tolerance")
- *     public static final FaultTolerance FT = FaultTolerance.create()
- *             .withRetry().maxRetries(2).done()
- *             .withFallback().handler(() -> "fallback").done()
- *             .build();
- * }
- * }
- * - * * Using a {@code static} producer field like this 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. *

- * Example of use: - * - * - *

{@code
- * @ApplicationScoped
- * public class MyService {
- *     @ApplyFaultTolerance("my-fault-tolerance")
- *     public String doSomething() {
- *         ...
- *     }
- * }
- * }
- * - * + * 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. *

From 4ddc7e927f1ac741d8e955e7263af2be32791427 Mon Sep 17 00:00:00 2001 From: Ladislav Thon Date: Thu, 31 Mar 2022 13:56:32 +0200 Subject: [PATCH 63/68] run tests with Java 18 and 19-EA --- .github/workflows/build.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7b0e6945..f96969e9 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -30,7 +30,8 @@ jobs: - 8 - 11 - 17 - - 18-ea + - 18 + - 19-ea os: - ubuntu-latest - windows-latest From 9f46cda1b783faceb6e7623c6ad2f05cbd7a3d6e Mon Sep 17 00:00:00 2001 From: Ladislav Thon Date: Thu, 31 Mar 2022 14:27:34 +0200 Subject: [PATCH 64/68] release 5.4.0, second attempt --- .github/project.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/project.yml b/.github/project.yml index 2ebdba9c..cc68d689 100644 --- a/.github/project.yml +++ b/.github/project.yml @@ -2,3 +2,4 @@ name: SmallRye Fault Tolerance release: current-version: 5.4.0 next-version: 5.4.1-SNAPSHOT + From 13171cc37dbd136e89984f5f8b9bb22141d9fc32 Mon Sep 17 00:00:00 2001 From: Ladislav Thon Date: Thu, 31 Mar 2022 15:00:40 +0200 Subject: [PATCH 65/68] make sure the Kotlin module generates a javadoc JAR --- .../api/ApplyFaultTolerance.java | 7 +++---- implementation/kotlin/pom.xml | 20 +++++++++++++++++++ pom.xml | 1 + 3 files changed, 24 insertions(+), 4 deletions(-) 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 9f55118b..03a21e20 100644 --- a/api/src/main/java/io/smallrye/faulttolerance/api/ApplyFaultTolerance.java +++ b/api/src/main/java/io/smallrye/faulttolerance/api/ApplyFaultTolerance.java @@ -20,10 +20,9 @@ * 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. - * Using a {@code static} producer field like this 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. + * 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 diff --git a/implementation/kotlin/pom.xml b/implementation/kotlin/pom.xml index f1d49610..a21ab040 100644 --- a/implementation/kotlin/pom.xml +++ b/implementation/kotlin/pom.xml @@ -40,6 +40,9 @@ + ${project.basedir}/src/main/kotlin + ${project.basedir}/src/test/kotlin + org.jetbrains.kotlin @@ -70,6 +73,23 @@ + + org.jetbrains.dokka + dokka-maven-plugin + ${version.kotlin-dokka} + + + attach-javadoc + package + + javadocJar + + + true + + + + org.apache.maven.plugins maven-compiler-plugin diff --git a/pom.xml b/pom.xml index 43bc0472..df876679 100644 --- a/pom.xml +++ b/pom.xml @@ -38,6 +38,7 @@ 3.4.3.Final 2.2.1.Final 1.6.10 + 1.6.10 1.6.0 3.0 2.0 From d46bf1bc30490142c8df3ca584aba8f3f6f5c756 Mon Sep 17 00:00:00 2001 From: Ladislav Thon Date: Thu, 31 Mar 2022 15:28:10 +0200 Subject: [PATCH 66/68] release 5.4.0, third attempt --- .github/project.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/project.yml b/.github/project.yml index cc68d689..2ebdba9c 100644 --- a/.github/project.yml +++ b/.github/project.yml @@ -2,4 +2,3 @@ name: SmallRye Fault Tolerance release: current-version: 5.4.0 next-version: 5.4.1-SNAPSHOT - From 16e1f6523ffa28144899c035dafd07d3a553dc7f Mon Sep 17 00:00:00 2001 From: SmallRye CI Date: Thu, 31 Mar 2022 14:08:40 +0000 Subject: [PATCH 67/68] Update antora.yml before release --- doc/antora.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/antora.yml b/doc/antora.yml index cb025901..b6c1504b 100644 --- a/doc/antora.yml +++ b/doc/antora.yml @@ -1,16 +1,16 @@ name: smallrye-fault-tolerance title: SmallRye Fault Tolerance -version: main +version: 5.4.0 nav: - modules/ROOT/nav.adoc asciidoc: attributes: smallrye-fault-tolerance: SmallRye Fault Tolerance - smallrye-fault-tolerance-version: '5.3.2' + smallrye-fault-tolerance-version: '5.4.0' microprofile-fault-tolerance: MicroProfile Fault Tolerance microprofile-fault-tolerance-version: '3.0' microprofile-fault-tolerance-url: https://download.eclipse.org/microprofile/microprofile-fault-tolerance-3.0/microprofile-fault-tolerance-spec-3.0.html - vertx4-version: '4.2.4' + vertx4-version: '4.2.6' From bc80bbb016bd46f5c89b46f4d8f517613724c533 Mon Sep 17 00:00:00 2001 From: SmallRye CI Date: Thu, 31 Mar 2022 14:10:30 +0000 Subject: [PATCH 68/68] [maven-release-plugin] prepare release 5.4.0 --- api/pom.xml | 2 +- implementation/autoconfig/core/pom.xml | 2 +- implementation/autoconfig/pom.xml | 2 +- implementation/autoconfig/processor/pom.xml | 2 +- implementation/context-propagation/pom.xml | 2 +- implementation/core/pom.xml | 2 +- implementation/fault-tolerance/pom.xml | 2 +- implementation/kotlin/pom.xml | 5 ++--- implementation/mutiny/pom.xml | 2 +- implementation/pom.xml | 2 +- implementation/rxjava3/pom.xml | 2 +- implementation/standalone/pom.xml | 2 +- implementation/tracing-propagation/pom.xml | 2 +- implementation/vertx/pom.xml | 2 +- pom.xml | 4 ++-- release/pom.xml | 2 +- testsuite/basic/pom.xml | 2 +- testsuite/integration/pom.xml | 2 +- testsuite/pom.xml | 2 +- testsuite/tck/pom.xml | 2 +- 20 files changed, 22 insertions(+), 23 deletions(-) diff --git a/api/pom.xml b/api/pom.xml index 7b31079b..f0e68c39 100644 --- a/api/pom.xml +++ b/api/pom.xml @@ -20,7 +20,7 @@ io.smallrye smallrye-fault-tolerance-parent - 5.3.3-SNAPSHOT + 5.4.0 smallrye-fault-tolerance-api diff --git a/implementation/autoconfig/core/pom.xml b/implementation/autoconfig/core/pom.xml index 3a34f295..8b04e70d 100644 --- a/implementation/autoconfig/core/pom.xml +++ b/implementation/autoconfig/core/pom.xml @@ -5,7 +5,7 @@ io.smallrye smallrye-fault-tolerance-autoconfig-parent - 5.3.3-SNAPSHOT + 5.4.0 smallrye-fault-tolerance-autoconfig-core diff --git a/implementation/autoconfig/pom.xml b/implementation/autoconfig/pom.xml index f301f85b..9b126f33 100644 --- a/implementation/autoconfig/pom.xml +++ b/implementation/autoconfig/pom.xml @@ -5,7 +5,7 @@ io.smallrye smallrye-fault-tolerance-implementation-parent - 5.3.3-SNAPSHOT + 5.4.0 smallrye-fault-tolerance-autoconfig-parent diff --git a/implementation/autoconfig/processor/pom.xml b/implementation/autoconfig/processor/pom.xml index 29cfbb0e..e0af7bf0 100644 --- a/implementation/autoconfig/processor/pom.xml +++ b/implementation/autoconfig/processor/pom.xml @@ -5,7 +5,7 @@ io.smallrye smallrye-fault-tolerance-autoconfig-parent - 5.3.3-SNAPSHOT + 5.4.0 smallrye-fault-tolerance-autoconfig-processor diff --git a/implementation/context-propagation/pom.xml b/implementation/context-propagation/pom.xml index 60156c2d..0c9f70fd 100644 --- a/implementation/context-propagation/pom.xml +++ b/implementation/context-propagation/pom.xml @@ -20,7 +20,7 @@ io.smallrye smallrye-fault-tolerance-implementation-parent - 5.3.3-SNAPSHOT + 5.4.0 smallrye-fault-tolerance-context-propagation diff --git a/implementation/core/pom.xml b/implementation/core/pom.xml index 65e0840f..bf324173 100644 --- a/implementation/core/pom.xml +++ b/implementation/core/pom.xml @@ -20,7 +20,7 @@ io.smallrye smallrye-fault-tolerance-implementation-parent - 5.3.3-SNAPSHOT + 5.4.0 smallrye-fault-tolerance-core diff --git a/implementation/fault-tolerance/pom.xml b/implementation/fault-tolerance/pom.xml index 9d1b7c78..afe2f81a 100644 --- a/implementation/fault-tolerance/pom.xml +++ b/implementation/fault-tolerance/pom.xml @@ -20,7 +20,7 @@ io.smallrye smallrye-fault-tolerance-implementation-parent - 5.3.3-SNAPSHOT + 5.4.0 smallrye-fault-tolerance diff --git a/implementation/kotlin/pom.xml b/implementation/kotlin/pom.xml index a21ab040..461f0fa6 100644 --- a/implementation/kotlin/pom.xml +++ b/implementation/kotlin/pom.xml @@ -1,12 +1,11 @@ - + 4.0.0 io.smallrye smallrye-fault-tolerance-implementation-parent - 5.3.3-SNAPSHOT + 5.4.0 smallrye-fault-tolerance-kotlin diff --git a/implementation/mutiny/pom.xml b/implementation/mutiny/pom.xml index 74bec8f0..c7d73c5b 100644 --- a/implementation/mutiny/pom.xml +++ b/implementation/mutiny/pom.xml @@ -5,7 +5,7 @@ io.smallrye smallrye-fault-tolerance-implementation-parent - 5.3.3-SNAPSHOT + 5.4.0 smallrye-fault-tolerance-mutiny diff --git a/implementation/pom.xml b/implementation/pom.xml index 00c31d78..2293e3e1 100644 --- a/implementation/pom.xml +++ b/implementation/pom.xml @@ -20,7 +20,7 @@ io.smallrye smallrye-fault-tolerance-parent - 5.3.3-SNAPSHOT + 5.4.0 smallrye-fault-tolerance-implementation-parent diff --git a/implementation/rxjava3/pom.xml b/implementation/rxjava3/pom.xml index 39e378cb..3650d22f 100644 --- a/implementation/rxjava3/pom.xml +++ b/implementation/rxjava3/pom.xml @@ -5,7 +5,7 @@ io.smallrye smallrye-fault-tolerance-implementation-parent - 5.3.3-SNAPSHOT + 5.4.0 smallrye-fault-tolerance-rxjava3 diff --git a/implementation/standalone/pom.xml b/implementation/standalone/pom.xml index 6fad48a0..76fe8a25 100644 --- a/implementation/standalone/pom.xml +++ b/implementation/standalone/pom.xml @@ -5,7 +5,7 @@ io.smallrye smallrye-fault-tolerance-implementation-parent - 5.3.3-SNAPSHOT + 5.4.0 smallrye-fault-tolerance-standalone diff --git a/implementation/tracing-propagation/pom.xml b/implementation/tracing-propagation/pom.xml index f6efa95c..fefb93c8 100644 --- a/implementation/tracing-propagation/pom.xml +++ b/implementation/tracing-propagation/pom.xml @@ -20,7 +20,7 @@ io.smallrye smallrye-fault-tolerance-implementation-parent - 5.3.3-SNAPSHOT + 5.4.0 smallrye-fault-tolerance-tracing-propagation diff --git a/implementation/vertx/pom.xml b/implementation/vertx/pom.xml index 3637c0e4..a37cf660 100644 --- a/implementation/vertx/pom.xml +++ b/implementation/vertx/pom.xml @@ -20,7 +20,7 @@ io.smallrye smallrye-fault-tolerance-implementation-parent - 5.3.3-SNAPSHOT + 5.4.0 smallrye-fault-tolerance-vertx diff --git a/pom.xml b/pom.xml index df876679..36a3e1fd 100644 --- a/pom.xml +++ b/pom.xml @@ -24,7 +24,7 @@ smallrye-fault-tolerance-parent - 5.3.3-SNAPSHOT + 5.4.0 pom SmallRye Fault Tolerance: Parent @@ -80,7 +80,7 @@ scm:git:git@github.com:smallrye/smallrye-fault-tolerance.git scm:git:git@github.com:smallrye/smallrye-fault-tolerance.git https://github.com/smallrye/smallrye-fault-tolerance/ - HEAD + 5.4.0 diff --git a/release/pom.xml b/release/pom.xml index 8dcf4e26..311f6ef1 100644 --- a/release/pom.xml +++ b/release/pom.xml @@ -5,7 +5,7 @@ io.smallrye smallrye-fault-tolerance-parent - 5.3.3-SNAPSHOT + 5.4.0 smallrye-fault-tolerance-release diff --git a/testsuite/basic/pom.xml b/testsuite/basic/pom.xml index e30e22d4..72380830 100644 --- a/testsuite/basic/pom.xml +++ b/testsuite/basic/pom.xml @@ -20,7 +20,7 @@ smallrye-fault-tolerance-testsuite-parent io.smallrye - 5.3.3-SNAPSHOT + 5.4.0 smallrye-fault-tolerance-testsuite-basic diff --git a/testsuite/integration/pom.xml b/testsuite/integration/pom.xml index c2f94d88..1d6fba79 100644 --- a/testsuite/integration/pom.xml +++ b/testsuite/integration/pom.xml @@ -5,7 +5,7 @@ smallrye-fault-tolerance-testsuite-parent io.smallrye - 5.3.3-SNAPSHOT + 5.4.0 smallrye-fault-tolerance-testsuite-integration diff --git a/testsuite/pom.xml b/testsuite/pom.xml index bf703859..47f21f58 100644 --- a/testsuite/pom.xml +++ b/testsuite/pom.xml @@ -5,7 +5,7 @@ smallrye-fault-tolerance-parent io.smallrye - 5.3.3-SNAPSHOT + 5.4.0 smallrye-fault-tolerance-testsuite-parent diff --git a/testsuite/tck/pom.xml b/testsuite/tck/pom.xml index 544acf9c..bb02d307 100644 --- a/testsuite/tck/pom.xml +++ b/testsuite/tck/pom.xml @@ -5,7 +5,7 @@ io.smallrye smallrye-fault-tolerance-testsuite-parent - 5.3.3-SNAPSHOT + 5.4.0 smallrye-fault-tolerance-tck