diff --git a/api/src/main/java/io/smallrye/faulttolerance/api/ApplyGuard.java b/api/src/main/java/io/smallrye/faulttolerance/api/ApplyGuard.java index 4a75cd3f..03c170b5 100644 --- a/api/src/main/java/io/smallrye/faulttolerance/api/ApplyGuard.java +++ b/api/src/main/java/io/smallrye/faulttolerance/api/ApplyGuard.java @@ -12,7 +12,7 @@ import io.smallrye.common.annotation.Experimental; /** - * A special interceptor binding annotation to apply preconfigured fault tolerance. + * An interceptor binding annotation to apply preconfigured fault tolerance. * If {@code @ApplyGuard("")} is present on a business method, * then a bean of type {@link Guard} or {@link TypedGuard} with qualifier * {@link io.smallrye.common.annotation.Identifier @Identifier("<identifier>")} @@ -30,12 +30,28 @@ * the one on the method takes precedence. *

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

+ * + * If {@code @Fallback} is present, it is used both by {@code Guard} and {@code TypedGuard} + * and it overrides the possible fallback configuration of {@code TypedGuard}. Further, + * the thread offload configuration of {@code Guard} or {@code TypedGuard} is ignored + * if the annotated method is asynchronous as determined from the method signature + * and possible annotations ({@code @Asynchronous} and {@code @AsynchronousNonBlocking}). + * The thread offload configuration of {@code Guard} or {@code TypedGuard} is only honored + * when the method cannot be determined to be asynchronous, but it still declares + * an asynchronous return type. *

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

* Note that this annotation has the same differences to standard MicroProfile Fault Tolerance * as {@code Guard} / {@code TypedGuard}: diff --git a/doc/modules/ROOT/pages/reference/programmatic-api.adoc b/doc/modules/ROOT/pages/reference/programmatic-api.adoc index ee95fd94..272e1866 100644 --- a/doc/modules/ROOT/pages/reference/programmatic-api.adoc +++ b/doc/modules/ROOT/pages/reference/programmatic-api.adoc @@ -266,6 +266,17 @@ In this case, it won't be possible to distinguish the different `Guard` objects If no description is provided, a random UUID is used. +== Differences to the Specification + +`Guard` and `TypedGuard` have the following differences to standard MicroProfile Fault Tolerance: + +* asynchronous actions of type `java.util.concurrent.Future` are not supported; +* the fallback, circuit breaker and retry strategies always inspect the cause chain of exceptions, following the behavior of SmallRye Fault Tolerance in the non-compatible mode. + +== Kotlin `suspend` Functions + +The `Guard` and `TypedGuard` APIs do not support Kotlin `suspend` functions at the moment. + == Integration Concerns 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/doc/modules/ROOT/pages/reference/reusable.adoc b/doc/modules/ROOT/pages/reference/reusable.adoc index 41d46325..a9406657 100644 --- a/doc/modules/ROOT/pages/reference/reusable.adoc +++ b/doc/modules/ROOT/pages/reference/reusable.adoc @@ -9,7 +9,7 @@ Each method has its own bulkhead, circuit breaker and/or rate limit, which is of The xref:reference/programmatic-api.adoc[programmatic API] of {smallrye-fault-tolerance} allows using a single `Guard` or `TypedGuard` object to guard multiple disparate actions, which allows reuse and state sharing. It is possible to use a programmatically constructed `Guard` or `TypedGuard` object declaratively, using the `@ApplyGuard` annotation. -To be able to do that, we need a bean of type `Guard` with the `@Identifier` qualifier: +To be able to do that, we need a bean of type `Guard` (or `TypedGuard`) with the `@Identifier` qualifier: [source,java] ---- @@ -46,10 +46,40 @@ public class MyService { } ---- +== Defining Fallback + Note that it is not possible to define a fallback on `Guard`, because fallback is tied to the action type. +It is possible to define a fallback on `TypedGuard`, because it can only be used to guard methods with a single return type, equal to the type the `TypedGuard` was created with. + +However, the `@ApplyGuard` annotation pays attention to the `@Fallback` annotation. +If `@Fallback` is defined, it is used both by `Guard` and `TypedGuard` instances, and it overrides the possible fallback defined on the `TypedGuard`. + +== Defining Thread Offload + +Both `Guard` and `TypedGuard` allow enabling or disabling thread offload. +This is ignored by `@ApplyGuard` if the annotated method is asynchronous as determined from the method signature and possible annotations (`@Asynchronous` and `@AsynchronousNonBlocking`). +See xref:reference/asynchronous.adoc[] for more information about how that determination works. + +The thread offload configuration of `Guard` or `TypedGuard` is only honored when the method cannot be determined to be asynchronous, but it still declares an asynchronous return type. + +.Compatible Mode +**** +In the compatible mode, methods are determined to be asynchronous when they are annotated `@Asynchronous` or `@AsynchronousNonBlocking` and they declare an asynchronous return type. +For such methods, the `Guard` / `TypedGuard` thread offload configuration does not apply. + +For methods that declare an asynchronous return type but are not annotated with `@Asynchronous` or `@AsynchronousNonBlocking`, the configuration on `Guard` / `TypedGuard` is honored. +**** -It is also possible to create a bean of type `TypedGuard<>` and apply it to methods that return the type `TypedGuard` was created with. -Note that `TypedGuard` allows defining a fallback, because it can only be used to guard methods with a single return type. +.Non-compatible Mode +**** +In the non-compatible mode, methods are determined to be asynchronous whenever they declare an asynchronous return type. +The `@Asynchronous` and `@AsynchronousNonBlocking` only affect whether the invocation of that method is offloaded to an extra thread. +If none of these annotations is present, no thread offload happens. + +In other words, in non-compatible mode, the `Guard` / `TypedGuard` thread offload configuration never applies. + +See xref:reference/non-compat.adoc[] for more information about the non-compatible mode. +**** == Metrics @@ -61,6 +91,17 @@ All methods annotated `@ApplyGuard` share the same bulkhead, circuit breaker and If the `Guard` or `TypedGuard` object used for `@ApplyGuard` is also used xref:reference/programmatic-api.adoc[programmatically], that usage is coalesced in metrics under the description as the `method` tag. +== Differences to the Specification + +`@ApplyGuard` has the same differences to standard MicroProfile Fault Tolerance as `Guard` / `TypedGuard`: + +* asynchronous actions of type `java.util.concurrent.Future` are not supported; +* the fallback, circuit breaker and retry strategies always inspect the cause chain of exceptions, following the behavior of SmallRye Fault Tolerance in the non-compatible mode. + +== Kotlin `suspend` Functions + +The `@ApplyGuard` API does not support Kotlin `suspend` functions at the moment. + [[migration_from_applyfaulttolerance]] == Migration from `@ApplyFaultTolerance` 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 1fb2f3d6..2c7b3e22 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 @@ -21,7 +21,6 @@ import io.smallrye.faulttolerance.api.CustomBackoffStrategy; import io.smallrye.faulttolerance.api.FaultTolerance; import io.smallrye.faulttolerance.api.RateLimitType; -import io.smallrye.faulttolerance.core.FailureContext; import io.smallrye.faulttolerance.core.FaultToleranceContext; import io.smallrye.faulttolerance.core.FaultToleranceStrategy; import io.smallrye.faulttolerance.core.Future; @@ -31,6 +30,7 @@ import io.smallrye.faulttolerance.core.circuit.breaker.CircuitBreaker; import io.smallrye.faulttolerance.core.circuit.breaker.CircuitBreakerEvents; import io.smallrye.faulttolerance.core.fallback.Fallback; +import io.smallrye.faulttolerance.core.fallback.FallbackFunction; import io.smallrye.faulttolerance.core.invocation.AsyncSupport; import io.smallrye.faulttolerance.core.invocation.AsyncSupportRegistry; import io.smallrye.faulttolerance.core.invocation.ConstantInvoker; @@ -364,7 +364,7 @@ private FaultToleranceStrategy buildSyncStrategy(BuilderLazyDependencies lazy // fallback is always enabled if (fallbackBuilder != null) { - Function> fallbackFunction = ctx -> { + FallbackFunction fallbackFunction = ctx -> { return Future.from(() -> fallbackBuilder.handler.apply(ctx.failure)); }; result = new Fallback<>(result, description, fallbackFunction, @@ -449,7 +449,7 @@ private FaultToleranceStrategy buildAsyncStrategy(BuilderLazyDependencies throw new FaultToleranceException("Unknown async type: " + asyncType); } - Function> fallbackFunction = ctx -> { + FallbackFunction fallbackFunction = ctx -> { try { return asyncSupport.toFuture(ConstantInvoker.of( fallbackBuilder.handler.apply(ctx.failure))); diff --git a/implementation/core/src/main/java/io/smallrye/faulttolerance/core/apiimpl/GuardCommon.java b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/apiimpl/GuardCommon.java index f891112a..f98283a0 100644 --- a/implementation/core/src/main/java/io/smallrye/faulttolerance/core/apiimpl/GuardCommon.java +++ b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/apiimpl/GuardCommon.java @@ -5,6 +5,7 @@ import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.concurrent.Callable; +import java.util.function.Consumer; import io.smallrye.faulttolerance.core.FaultToleranceContext; import io.smallrye.faulttolerance.core.FaultToleranceStrategy; @@ -13,9 +14,10 @@ import io.smallrye.faulttolerance.core.invocation.AsyncSupportRegistry; import io.smallrye.faulttolerance.core.invocation.Invoker; import io.smallrye.faulttolerance.core.invocation.StrategyInvoker; -import io.smallrye.faulttolerance.core.metrics.MeteredOperationName; final class GuardCommon { + private static final Class[] NO_PARAMS = new Class[0]; + // V = value type, e.g. String // T = result type, e.g. String or CompletionStage or Uni // @@ -23,11 +25,11 @@ final class GuardCommon { // in asynchronous scenario, T is an async type that eventually produces V static AsyncSupport asyncSupport(Type type) { if (type instanceof Class) { - return AsyncSupportRegistry.get(new Class[0], (Class) type); + return AsyncSupportRegistry.get(NO_PARAMS, (Class) type); } else if (type instanceof ParameterizedType) { ParameterizedType parameterizedType = (ParameterizedType) type; Class rawType = (Class) parameterizedType.getRawType(); - return AsyncSupportRegistry.get(new Class[0], rawType); + return AsyncSupportRegistry.get(NO_PARAMS, rawType); } else { return null; } @@ -39,11 +41,11 @@ static AsyncSupport asyncSupport(Type type) { // in synchronous scenario, V = T // in asynchronous scenario, T is an async type that eventually produces V static T guard(Callable action, FaultToleranceStrategy strategy, AsyncSupport asyncSupport, - EventHandlers eventHandlers, MeteredOperationName meteredOperationName) throws Exception { + EventHandlers eventHandlers, Consumer> contextModifier) throws Exception { if (asyncSupport == null) { FaultToleranceContext ctx = new FaultToleranceContext<>(() -> Future.from(action), false); - if (meteredOperationName != null) { - ctx.set(MeteredOperationName.class, meteredOperationName); + if (contextModifier != null) { + contextModifier.accept(ctx); } eventHandlers.register(ctx); try { @@ -59,8 +61,8 @@ static T guard(Callable action, FaultToleranceStrategy strategy, As Invoker invoker = new CallableInvoker<>(action); FaultToleranceContext ctx = new FaultToleranceContext<>(() -> asyncSupport.toFuture(invoker), true); ctx.set(AsyncSupport.class, asyncSupport); - if (meteredOperationName != null) { - ctx.set(MeteredOperationName.class, meteredOperationName); + if (contextModifier != null) { + contextModifier.accept(ctx); } eventHandlers.register(ctx); Invoker> wrapper = new StrategyInvoker<>(null, strategy, ctx); diff --git a/implementation/core/src/main/java/io/smallrye/faulttolerance/core/apiimpl/GuardImpl.java b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/apiimpl/GuardImpl.java index 35f822bb..e59875fb 100644 --- a/implementation/core/src/main/java/io/smallrye/faulttolerance/core/apiimpl/GuardImpl.java +++ b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/apiimpl/GuardImpl.java @@ -21,6 +21,7 @@ import io.smallrye.faulttolerance.api.CustomBackoffStrategy; import io.smallrye.faulttolerance.api.Guard; import io.smallrye.faulttolerance.api.RateLimitType; +import io.smallrye.faulttolerance.core.FaultToleranceContext; import io.smallrye.faulttolerance.core.FaultToleranceStrategy; import io.smallrye.faulttolerance.core.async.RememberEventLoop; import io.smallrye.faulttolerance.core.async.SyncAsyncSplit; @@ -28,10 +29,11 @@ import io.smallrye.faulttolerance.core.bulkhead.Bulkhead; import io.smallrye.faulttolerance.core.circuit.breaker.CircuitBreaker; import io.smallrye.faulttolerance.core.circuit.breaker.CircuitBreakerEvents; +import io.smallrye.faulttolerance.core.fallback.Fallback; +import io.smallrye.faulttolerance.core.fallback.FallbackFunction; import io.smallrye.faulttolerance.core.invocation.AsyncSupport; import io.smallrye.faulttolerance.core.metrics.DelegatingMetricsCollector; import io.smallrye.faulttolerance.core.metrics.MeteredOperation; -import io.smallrye.faulttolerance.core.metrics.MeteredOperationName; import io.smallrye.faulttolerance.core.metrics.MetricsProvider; import io.smallrye.faulttolerance.core.rate.limit.RateLimit; import io.smallrye.faulttolerance.core.retry.BackOff; @@ -81,10 +83,11 @@ public class GuardImpl implements Guard { this.eventHandlers = eventHandlers; } - public T guard(Callable action, Type valueType, MeteredOperationName meteredOperationName) throws Exception { + public T guard(Callable action, Type valueType, Consumer> contextModifier) + throws Exception { AsyncSupport asyncSupport = GuardCommon.asyncSupport(valueType); return GuardCommon.guard(action, (FaultToleranceStrategy) strategy, asyncSupport, eventHandlers, - meteredOperationName); + contextModifier); } @Override @@ -218,10 +221,8 @@ final FaultToleranceStrategy buildStrategy(BuilderLazyDependencies lazyDe FaultToleranceStrategy result = invocation(); // thread offload is always enabled - if (offloadToAnotherThread) { - Executor executor = offloadExecutor != null ? offloadExecutor : lazyDependencies.asyncExecutor(); - result = new SyncAsyncSplit<>(new ThreadOffload<>(result, executor), result); - } + Executor executor = offloadExecutor != null ? offloadExecutor : lazyDependencies.asyncExecutor(); + result = new SyncAsyncSplit<>(new ThreadOffload<>(result, executor, offloadToAnotherThread), result); if (lazyDependencies.ftEnabled() && bulkheadBuilder != null) { result = new Bulkhead<>(result, description, @@ -275,7 +276,8 @@ final FaultToleranceStrategy buildStrategy(BuilderLazyDependencies lazyDe retryBuilder.beforeRetry != null ? ctx -> retryBuilder.beforeRetry.accept(ctx.failure) : null); } - // no fallback here + // fallback is always enabled + result = new Fallback<>(result, description, FallbackFunction.ignore(), ExceptionDecision.IGNORE); MetricsProvider metricsProvider = lazyDependencies.metricsProvider(); if (metricsProvider.isEnabled()) { diff --git a/implementation/core/src/main/java/io/smallrye/faulttolerance/core/apiimpl/TypedGuardImpl.java b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/apiimpl/TypedGuardImpl.java index b0b9773a..59395d9c 100644 --- a/implementation/core/src/main/java/io/smallrye/faulttolerance/core/apiimpl/TypedGuardImpl.java +++ b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/apiimpl/TypedGuardImpl.java @@ -20,7 +20,7 @@ import io.smallrye.faulttolerance.api.CustomBackoffStrategy; import io.smallrye.faulttolerance.api.RateLimitType; import io.smallrye.faulttolerance.api.TypedGuard; -import io.smallrye.faulttolerance.core.FailureContext; +import io.smallrye.faulttolerance.core.FaultToleranceContext; import io.smallrye.faulttolerance.core.FaultToleranceStrategy; import io.smallrye.faulttolerance.core.Future; import io.smallrye.faulttolerance.core.async.RememberEventLoop; @@ -30,11 +30,11 @@ import io.smallrye.faulttolerance.core.circuit.breaker.CircuitBreaker; import io.smallrye.faulttolerance.core.circuit.breaker.CircuitBreakerEvents; import io.smallrye.faulttolerance.core.fallback.Fallback; +import io.smallrye.faulttolerance.core.fallback.FallbackFunction; import io.smallrye.faulttolerance.core.invocation.AsyncSupport; import io.smallrye.faulttolerance.core.invocation.ConstantInvoker; import io.smallrye.faulttolerance.core.metrics.DelegatingMetricsCollector; import io.smallrye.faulttolerance.core.metrics.MeteredOperation; -import io.smallrye.faulttolerance.core.metrics.MeteredOperationName; import io.smallrye.faulttolerance.core.metrics.MetricsProvider; import io.smallrye.faulttolerance.core.rate.limit.RateLimit; import io.smallrye.faulttolerance.core.retry.BackOff; @@ -91,8 +91,8 @@ public final class TypedGuardImpl implements TypedGuard { this.eventHandlers = eventHandlers; } - public T guard(Callable action, MeteredOperationName meteredOperationName) throws Exception { - return GuardCommon.guard(action, strategy, asyncSupport, eventHandlers, meteredOperationName); + public T guard(Callable action, Consumer> contextModifier) throws Exception { + return GuardCommon.guard(action, strategy, asyncSupport, eventHandlers, contextModifier); } @Override @@ -223,10 +223,8 @@ final FaultToleranceStrategy buildStrategy(BuilderLazyDependencies lazyDepend FaultToleranceStrategy result = invocation(); // thread offload is always enabled - if (offloadToAnotherThread) { - Executor executor = offloadExecutor != null ? offloadExecutor : lazyDependencies.asyncExecutor(); - result = new SyncAsyncSplit<>(new ThreadOffload<>(result, executor), result); - } + Executor executor = offloadExecutor != null ? offloadExecutor : lazyDependencies.asyncExecutor(); + result = new SyncAsyncSplit<>(new ThreadOffload<>(result, executor, offloadToAnotherThread), result); if (lazyDependencies.ftEnabled() && bulkheadBuilder != null) { result = new Bulkhead<>(result, description, @@ -281,8 +279,9 @@ final FaultToleranceStrategy buildStrategy(BuilderLazyDependencies lazyDepend } // fallback is always enabled + FallbackFunction fallbackFunction = FallbackFunction.ignore(); + ExceptionDecision exceptionDecision = ExceptionDecision.IGNORE; if (fallbackBuilder != null) { - Function> fallbackFunction; if (asyncSupport != null) { fallbackFunction = ctx -> { try { @@ -298,10 +297,10 @@ final FaultToleranceStrategy buildStrategy(BuilderLazyDependencies lazyDepend }; } - result = new Fallback<>(result, description, fallbackFunction, - createExceptionDecision(fallbackBuilder.skipOn, fallbackBuilder.applyOn, - fallbackBuilder.whenPredicate)); + exceptionDecision = createExceptionDecision(fallbackBuilder.skipOn, fallbackBuilder.applyOn, + fallbackBuilder.whenPredicate); } + result = new Fallback<>(result, description, fallbackFunction, exceptionDecision); MetricsProvider metricsProvider = lazyDependencies.metricsProvider(); if (metricsProvider.isEnabled()) { diff --git a/implementation/core/src/main/java/io/smallrye/faulttolerance/core/async/ThreadOffload.java b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/async/ThreadOffload.java index 26b72a84..39f1465d 100644 --- a/implementation/core/src/main/java/io/smallrye/faulttolerance/core/async/ThreadOffload.java +++ b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/async/ThreadOffload.java @@ -17,14 +17,25 @@ public class ThreadOffload implements FaultToleranceStrategy { private final FaultToleranceStrategy delegate; private final Executor executor; + private final ThreadOffloadEnabled defaultEnabled; public ThreadOffload(FaultToleranceStrategy delegate, Executor executor) { + this(delegate, executor, true); + } + + public ThreadOffload(FaultToleranceStrategy delegate, Executor executor, boolean defaultEnabled) { this.delegate = delegate; this.executor = checkNotNull(executor, "Executor must be set"); + this.defaultEnabled = new ThreadOffloadEnabled(defaultEnabled); } @Override public Future apply(FaultToleranceContext ctx) { + // required for `@ApplyGuard` + if (!ctx.get(ThreadOffloadEnabled.class, defaultEnabled).value) { + return delegate.apply(ctx); + } + LOG.trace("ThreadOffload started"); try { Executor executor = ctx.get(Executor.class, this.executor); diff --git a/implementation/core/src/main/java/io/smallrye/faulttolerance/core/async/ThreadOffloadEnabled.java b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/async/ThreadOffloadEnabled.java new file mode 100644 index 00000000..9bfd91c6 --- /dev/null +++ b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/async/ThreadOffloadEnabled.java @@ -0,0 +1,9 @@ +package io.smallrye.faulttolerance.core.async; + +public final class ThreadOffloadEnabled { + public final boolean value; + + public ThreadOffloadEnabled(boolean value) { + this.value = value; + } +} 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 6f920363..ec46a694 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 @@ -3,8 +3,6 @@ import static io.smallrye.faulttolerance.core.fallback.FallbackLogger.LOG; import static io.smallrye.faulttolerance.core.util.Preconditions.checkNotNull; -import java.util.function.Function; - import io.smallrye.faulttolerance.core.Completer; import io.smallrye.faulttolerance.core.FailureContext; import io.smallrye.faulttolerance.core.FaultToleranceContext; @@ -16,11 +14,11 @@ public class Fallback implements FaultToleranceStrategy { private final FaultToleranceStrategy delegate; private final String description; - private final Function> fallback; + private final FallbackFunction fallback; private final ExceptionDecision exceptionDecision; public Fallback(FaultToleranceStrategy delegate, String description, - Function> fallback, ExceptionDecision exceptionDecision) { + FallbackFunction fallback, 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"); @@ -29,6 +27,14 @@ public Fallback(FaultToleranceStrategy delegate, String description, @Override public Future apply(FaultToleranceContext ctx) { + FallbackFunction fallback = ctx.get(FallbackFunction.class, this.fallback); + ExceptionDecision exceptionDecision = ctx.get(ExceptionDecision.class, this.exceptionDecision); + + // required for `@ApplyGuard` + if (fallback == FallbackFunction.IGNORE || exceptionDecision == ExceptionDecision.IGNORE) { + return delegate.apply(ctx); + } + LOG.trace("Fallback started"); try { ctx.fireEvent(FallbackEvents.Defined.INSTANCE); diff --git a/implementation/core/src/main/java/io/smallrye/faulttolerance/core/fallback/FallbackFunction.java b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/fallback/FallbackFunction.java new file mode 100644 index 00000000..2bf4100d --- /dev/null +++ b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/fallback/FallbackFunction.java @@ -0,0 +1,17 @@ +package io.smallrye.faulttolerance.core.fallback; + +import java.util.function.Function; + +import io.smallrye.faulttolerance.core.FailureContext; +import io.smallrye.faulttolerance.core.Future; + +@FunctionalInterface +public interface FallbackFunction extends Function> { + FallbackFunction IGNORE = ignored -> { + throw new UnsupportedOperationException(); + }; + + static FallbackFunction ignore() { + return (FallbackFunction) IGNORE; + } +} diff --git a/implementation/core/src/main/java/io/smallrye/faulttolerance/core/fallback/ThreadOffloadFallbackFunction.java b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/fallback/ThreadOffloadFallbackFunction.java index de935b47..41417c7a 100644 --- a/implementation/core/src/main/java/io/smallrye/faulttolerance/core/fallback/ThreadOffloadFallbackFunction.java +++ b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/fallback/ThreadOffloadFallbackFunction.java @@ -7,7 +7,7 @@ import io.smallrye.faulttolerance.core.FailureContext; import io.smallrye.faulttolerance.core.Future; -public final class ThreadOffloadFallbackFunction implements Function> { +public final class ThreadOffloadFallbackFunction implements FallbackFunction { private final Function> delegate; private final Executor executor; 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 8f8285f4..226c9a66 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 @@ -5,4 +5,8 @@ public interface ExceptionDecision { ExceptionDecision ALWAYS_EXPECTED = ignored -> true; ExceptionDecision ALWAYS_FAILURE = ignored -> false; + + ExceptionDecision IGNORE = ignored -> { + throw new UnsupportedOperationException(); + }; } diff --git a/implementation/core/src/test/java/io/smallrye/faulttolerance/core/fallback/FallbackSyncTest.java b/implementation/core/src/test/java/io/smallrye/faulttolerance/core/fallback/FallbackSyncTest.java index d068842e..cecbaf26 100644 --- a/implementation/core/src/test/java/io/smallrye/faulttolerance/core/fallback/FallbackSyncTest.java +++ b/implementation/core/src/test/java/io/smallrye/faulttolerance/core/fallback/FallbackSyncTest.java @@ -5,11 +5,8 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import java.util.function.Function; - import org.junit.jupiter.api.Test; -import io.smallrye.faulttolerance.core.FailureContext; import io.smallrye.faulttolerance.core.FaultToleranceStrategy; import io.smallrye.faulttolerance.core.Future; import io.smallrye.faulttolerance.core.util.ExceptionDecision; @@ -117,7 +114,7 @@ public void waitingOnParty_interruptedInInvocation() throws InterruptedException public void waitingOnParty_interruptedInFallback() throws InterruptedException { TestInvocation invocation = TestInvocation.of(TestException::doThrow); Party party = Party.create(1); - Function> fallbackFunction = ctx -> { + FallbackFunction fallbackFunction = ctx -> { try { party.participant().attend(); return Future.of("fallback"); @@ -160,7 +157,7 @@ public void selfInterruptedInInvocation_exception() { @Test public void selfInterruptedInFallback_value() throws Exception { TestInvocation invocation = TestInvocation.of(TestException::doThrow); - Function> fallbackFunction = ctx -> { + FallbackFunction fallbackFunction = ctx -> { Thread.currentThread().interrupt(); return Future.of("fallback"); }; @@ -173,7 +170,7 @@ public void selfInterruptedInFallback_value() throws Exception { @Test public void selfInterruptedInFallback_exception() { TestInvocation invocation = TestInvocation.of(TestException::doThrow); - Function> fallbackFunction = ctx -> { + FallbackFunction fallbackFunction = ctx -> { Thread.currentThread().interrupt(); throw new RuntimeException(); }; 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 47efa968..653b7da2 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 @@ -27,7 +27,6 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.function.Consumer; -import java.util.function.Function; import java.util.function.Predicate; import java.util.function.Supplier; @@ -67,11 +66,13 @@ import io.smallrye.faulttolerance.core.async.FutureExecution; import io.smallrye.faulttolerance.core.async.RememberEventLoop; import io.smallrye.faulttolerance.core.async.ThreadOffload; +import io.smallrye.faulttolerance.core.async.ThreadOffloadEnabled; import io.smallrye.faulttolerance.core.bulkhead.Bulkhead; import io.smallrye.faulttolerance.core.circuit.breaker.CircuitBreaker; import io.smallrye.faulttolerance.core.circuit.breaker.CircuitBreakerEvents; import io.smallrye.faulttolerance.core.event.loop.EventLoop; import io.smallrye.faulttolerance.core.fallback.Fallback; +import io.smallrye.faulttolerance.core.fallback.FallbackFunction; import io.smallrye.faulttolerance.core.fallback.ThreadOffloadFallbackFunction; import io.smallrye.faulttolerance.core.invocation.AsyncSupport; import io.smallrye.faulttolerance.core.invocation.AsyncSupportRegistry; @@ -195,7 +196,7 @@ public Object intercept(InvocationContext interceptionContext) throws Throwable if (operation.hasApplyFaultTolerance()) { return applyFaultToleranceFlow(operation, interceptionContext); } else if (operation.hasApplyGuard()) { - return applyGuardFlow(operation, interceptionContext); + return applyGuardFlow(operation, interceptionContext, point); } else if (specCompatibility.isOperationTrulyAsynchronous(operation)) { return asyncFlow(operation, interceptionContext, point); } else if (specCompatibility.isOperationPseudoAsynchronous(operation)) { @@ -255,7 +256,8 @@ private T applyFaultToleranceFlow(FaultToleranceOperation operation, Invo // // in synchronous scenario, V = T // in asynchronous scenario, T is an async type that eventually produces V - private T applyGuardFlow(FaultToleranceOperation operation, InvocationContext interceptionContext) throws Exception { + private T applyGuardFlow(FaultToleranceOperation operation, InvocationContext interceptionContext, + InterceptionPoint point) throws Exception { String identifier = operation.getApplyGuard().value(); Instance guardInstance = configuredGuard.select(Identifier.Literal.of(identifier)); Instance> typedGuardInstance = (Instance) configuredTypedGuard.select(Identifier.Literal.of(identifier)); @@ -265,8 +267,40 @@ private T applyGuardFlow(FaultToleranceOperation operation, InvocationCon + " with qualifier @" + Identifier.class.getName() + "(\"" + identifier + "\")"); } + FallbackFunction fallbackFunction; + ExceptionDecision exceptionDecision; + if (operation.hasFallback()) { + fallbackFunction = cache.getFallbackFunction(point, + () -> prepareFallbackFunction(point, operation)); + exceptionDecision = cache.getFallbackExceptionDecision(point, + () -> createExceptionDecision(operation.getFallback().skipOn(), operation.getFallback().applyOn())); + } else { + fallbackFunction = null; + exceptionDecision = null; + } + Boolean threadOffload; + if (specCompatibility.isOperationTrulyAsynchronous(operation)) { + threadOffload = operation.isThreadOffloadRequired(); + } else { + threadOffload = null; + } MeteredOperationName meteredOperationName = new MeteredOperationName(operation.getName()); + Consumer> contextModifier = ctx -> { + ctx.set(InvocationContext.class, interceptionContext); + + if (fallbackFunction != null) { + ctx.set(FallbackFunction.class, fallbackFunction); + } + if (exceptionDecision != null) { + ctx.set(ExceptionDecision.class, exceptionDecision); + } + if (threadOffload != null) { + ctx.set(ThreadOffloadEnabled.class, new ThreadOffloadEnabled(threadOffload)); + } + ctx.set(MeteredOperationName.class, meteredOperationName); + }; + if (guardInstance.isResolvable()) { Guard guard = guardInstance.get(); if (!(guard instanceof LazyGuard)) { @@ -275,8 +309,7 @@ private T applyGuardFlow(FaultToleranceOperation operation, InvocationCon } GuardImpl guardImpl = ((LazyGuard) guard).instance(); - return guardImpl.guard(() -> (T) interceptionContext.proceed(), - operation.getReturnType(), meteredOperationName); + return guardImpl.guard(() -> (T) interceptionContext.proceed(), operation.getReturnType(), contextModifier); } else /* typedGuardInstance.isResolvable() */ { TypedGuard guard = typedGuardInstance.get(); if (!(guard instanceof LazyTypedGuard)) { @@ -285,9 +318,7 @@ private T applyGuardFlow(FaultToleranceOperation operation, InvocationCon } TypedGuardImpl guardImpl = ((LazyTypedGuard) guard).instance(); - return guardImpl.guard(() -> { - return (T) interceptionContext.proceed(); - }, meteredOperationName); + return guardImpl.guard(() -> (T) interceptionContext.proceed(), contextModifier); } } @@ -551,7 +582,7 @@ private Supplier prepareRetryBackoff(FaultToleranceOperation operation) // // in synchronous scenario, V = T // in asynchronous scenario, T is an async type that eventually produces V - private Function> prepareFallbackFunction( + private FallbackFunction prepareFallbackFunction( InterceptionPoint point, FaultToleranceOperation operation) { AsyncSupport asyncSupport = cache.getAsyncSupport(point, operation); @@ -560,7 +591,7 @@ private Function> prepareFallbackFunction( ? cache.getFallbackMethodCandidates(point, operation) : null; - Function> fallbackFunction; + FallbackFunction fallbackFunction; if (candidates != null) { fallbackFunction = ctx -> { diff --git a/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/internal/StrategyCache.java b/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/internal/StrategyCache.java index 7c6331d2..5b69a444 100644 --- a/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/internal/StrategyCache.java +++ b/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/internal/StrategyCache.java @@ -10,8 +10,10 @@ import io.smallrye.faulttolerance.SpecCompatibility; import io.smallrye.faulttolerance.config.FaultToleranceOperation; import io.smallrye.faulttolerance.core.FaultToleranceStrategy; +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.util.ExceptionDecision; @Singleton public class StrategyCache { @@ -19,6 +21,8 @@ public class StrategyCache { private final Map fallbackMethods = new ConcurrentHashMap<>(); private final Map beforeRetryMethods = new ConcurrentHashMap<>(); private final Map> asyncSupports = new ConcurrentHashMap<>(); + private final Map> fallbackFunctions = new ConcurrentHashMap<>(); + private final Map fallbackExceptionDecisions = new ConcurrentHashMap<>(); private final SpecCompatibility specCompatibility; @@ -28,8 +32,7 @@ public StrategyCache(SpecCompatibility specCompatibility) { } @SuppressWarnings("unchecked") - public FaultToleranceStrategy getStrategy(InterceptionPoint point, - Supplier> producer) { + public FaultToleranceStrategy getStrategy(InterceptionPoint point, Supplier> producer) { return (FaultToleranceStrategy) strategies.computeIfAbsent(point, ignored -> producer.get()); } @@ -43,7 +46,18 @@ public BeforeRetryMethod getBeforeRetryMethod(InterceptionPoint point, FaultTole } public AsyncSupport getAsyncSupport(InterceptionPoint point, FaultToleranceOperation operation) { - return (AsyncSupport) asyncSupports.computeIfAbsent(point, + AsyncSupport asyncSupport = asyncSupports.computeIfAbsent(point, ignored -> AsyncSupportRegistry.get(operation.getParameterTypes(), operation.getReturnType())); + return (AsyncSupport) asyncSupport; + } + + // only used with `@ApplyGuard`, useless otherwise + public FallbackFunction getFallbackFunction(InterceptionPoint point, Supplier> producer) { + return (FallbackFunction) fallbackFunctions.computeIfAbsent(point, ignored -> producer.get()); + } + + // only used with `@ApplyGuard`, useless otherwise + public ExceptionDecision getFallbackExceptionDecision(InterceptionPoint point, Supplier producer) { + return fallbackExceptionDecisions.computeIfAbsent(point, ignored -> producer.get()); } } diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/completionstage/fallback/guard/MyFaultTolerance.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/completionstage/fallback/guard/MyFaultTolerance.java new file mode 100644 index 00000000..e160480e --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/completionstage/fallback/guard/MyFaultTolerance.java @@ -0,0 +1,16 @@ +package io.smallrye.faulttolerance.reuse.async.completionstage.fallback.guard; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.inject.Produces; + +import io.smallrye.common.annotation.Identifier; +import io.smallrye.faulttolerance.api.Guard; + +@ApplicationScoped +public class MyFaultTolerance { + @Produces + @Identifier("my-fault-tolerance") + public static final Guard GUARD = Guard.create() + .withRetry().maxRetries(2).done() + .build(); +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/completionstage/fallback/guard/MyService.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/completionstage/fallback/guard/MyService.java new file mode 100644 index 00000000..ff74b2e1 --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/completionstage/fallback/guard/MyService.java @@ -0,0 +1,29 @@ +package io.smallrye.faulttolerance.reuse.async.completionstage.fallback.guard; + +import static java.util.concurrent.CompletableFuture.completedFuture; +import static java.util.concurrent.CompletableFuture.failedFuture; + +import java.util.concurrent.CompletionStage; +import java.util.concurrent.atomic.AtomicInteger; + +import jakarta.enterprise.context.ApplicationScoped; + +import org.eclipse.microprofile.faulttolerance.Fallback; + +import io.smallrye.faulttolerance.api.ApplyGuard; + +@ApplicationScoped +public class MyService { + static final AtomicInteger COUNTER = new AtomicInteger(0); + + @ApplyGuard("my-fault-tolerance") + @Fallback(fallbackMethod = "fallback") + public CompletionStage hello() { + COUNTER.incrementAndGet(); + return failedFuture(new IllegalArgumentException()); + } + + CompletionStage fallback() { + return completedFuture("better fallback"); + } +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/completionstage/fallback/guard/ReuseAsyncCompletionStageFallbackGuardTest.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/completionstage/fallback/guard/ReuseAsyncCompletionStageFallbackGuardTest.java new file mode 100644 index 00000000..48cc44ad --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/completionstage/fallback/guard/ReuseAsyncCompletionStageFallbackGuardTest.java @@ -0,0 +1,22 @@ +package io.smallrye.faulttolerance.reuse.async.completionstage.fallback.guard; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.concurrent.TimeUnit; + +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 ReuseAsyncCompletionStageFallbackGuardTest { + @Test + public void test(MyService service) { + assertThat(service.hello()) + .succeedsWithin(1, TimeUnit.SECONDS) + .isEqualTo("better fallback"); + assertThat(MyService.COUNTER).hasValue(3); + } +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/completionstage/fallback/typedguard/MyFaultTolerance.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/completionstage/fallback/typedguard/MyFaultTolerance.java new file mode 100644 index 00000000..daf75dd8 --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/completionstage/fallback/typedguard/MyFaultTolerance.java @@ -0,0 +1,22 @@ +package io.smallrye.faulttolerance.reuse.async.completionstage.fallback.typedguard; + +import static java.util.concurrent.CompletableFuture.completedFuture; + +import java.util.concurrent.CompletionStage; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.inject.Produces; + +import io.smallrye.common.annotation.Identifier; +import io.smallrye.faulttolerance.Types; +import io.smallrye.faulttolerance.api.TypedGuard; + +@ApplicationScoped +public class MyFaultTolerance { + @Produces + @Identifier("my-fault-tolerance") + public static final TypedGuard> GUARD = TypedGuard.create(Types.CS_STRING) + .withRetry().maxRetries(2).done() + .withFallback().handler(() -> completedFuture("fallback")).done() + .build(); +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/completionstage/fallback/typedguard/MyService.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/completionstage/fallback/typedguard/MyService.java new file mode 100644 index 00000000..5328147f --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/completionstage/fallback/typedguard/MyService.java @@ -0,0 +1,29 @@ +package io.smallrye.faulttolerance.reuse.async.completionstage.fallback.typedguard; + +import static java.util.concurrent.CompletableFuture.completedFuture; +import static java.util.concurrent.CompletableFuture.failedFuture; + +import java.util.concurrent.CompletionStage; +import java.util.concurrent.atomic.AtomicInteger; + +import jakarta.enterprise.context.ApplicationScoped; + +import org.eclipse.microprofile.faulttolerance.Fallback; + +import io.smallrye.faulttolerance.api.ApplyGuard; + +@ApplicationScoped +public class MyService { + static final AtomicInteger COUNTER = new AtomicInteger(0); + + @ApplyGuard("my-fault-tolerance") + @Fallback(fallbackMethod = "fallback") + public CompletionStage hello() { + COUNTER.incrementAndGet(); + return failedFuture(new IllegalArgumentException()); + } + + CompletionStage fallback() { + return completedFuture("better fallback"); + } +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/completionstage/fallback/typedguard/ReuseAsyncCompletionStageFallbackTypedGuardTest.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/completionstage/fallback/typedguard/ReuseAsyncCompletionStageFallbackTypedGuardTest.java new file mode 100644 index 00000000..964c597a --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/completionstage/fallback/typedguard/ReuseAsyncCompletionStageFallbackTypedGuardTest.java @@ -0,0 +1,22 @@ +package io.smallrye.faulttolerance.reuse.async.completionstage.fallback.typedguard; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.concurrent.TimeUnit; + +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 ReuseAsyncCompletionStageFallbackTypedGuardTest { + @Test + public void test(MyService service) { + assertThat(service.hello()) + .succeedsWithin(1, TimeUnit.SECONDS) + .isEqualTo("better fallback"); + assertThat(MyService.COUNTER).hasValue(3); + } +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/completionstage/threadoffload/guard/MyFaultTolerance.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/completionstage/threadoffload/guard/MyFaultTolerance.java new file mode 100644 index 00000000..64a81598 --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/completionstage/threadoffload/guard/MyFaultTolerance.java @@ -0,0 +1,17 @@ +package io.smallrye.faulttolerance.reuse.async.completionstage.threadoffload.guard; + +import jakarta.enterprise.inject.Produces; +import jakarta.inject.Singleton; + +import io.smallrye.common.annotation.Identifier; +import io.smallrye.faulttolerance.api.Guard; + +@Singleton +public class MyFaultTolerance { + // not `static` to create a new instance for each Weld container in the test + @Produces + @Identifier("my-fault-tolerance") + public final Guard GUARD = Guard.create() + .withThreadOffload(true) + .build(); +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/completionstage/threadoffload/guard/MyService.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/completionstage/threadoffload/guard/MyService.java new file mode 100644 index 00000000..0a21dbee --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/completionstage/threadoffload/guard/MyService.java @@ -0,0 +1,40 @@ +package io.smallrye.faulttolerance.reuse.async.completionstage.threadoffload.guard; + +import static java.util.concurrent.CompletableFuture.completedFuture; + +import java.util.concurrent.CompletionStage; +import java.util.concurrent.atomic.AtomicReference; + +import jakarta.enterprise.context.ApplicationScoped; + +import org.eclipse.microprofile.faulttolerance.Asynchronous; + +import io.smallrye.faulttolerance.api.ApplyGuard; +import io.smallrye.faulttolerance.api.AsynchronousNonBlocking; + +@ApplicationScoped +public class MyService { + static final AtomicReference CURRENT_THREAD = new AtomicReference<>(); + static final AtomicReference CURRENT_THREAD_BLOCKING = new AtomicReference<>(); + static final AtomicReference CURRENT_THREAD_NONBLOCKING = new AtomicReference<>(); + + @ApplyGuard("my-fault-tolerance") + public CompletionStage hello() { + CURRENT_THREAD.set(Thread.currentThread()); + return completedFuture("hello"); + } + + @ApplyGuard("my-fault-tolerance") + @Asynchronous + public CompletionStage helloBlocking() { + CURRENT_THREAD_BLOCKING.set(Thread.currentThread()); + return completedFuture("hello"); + } + + @ApplyGuard("my-fault-tolerance") + @AsynchronousNonBlocking + public CompletionStage helloNonBlocking() { + CURRENT_THREAD_NONBLOCKING.set(Thread.currentThread()); + return completedFuture("hello"); + } +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/completionstage/threadoffload/guard/ReuseAsyncCompletionStageThreadOffloadGuardTest.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/completionstage/threadoffload/guard/ReuseAsyncCompletionStageThreadOffloadGuardTest.java new file mode 100644 index 00000000..f4db4de3 --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/completionstage/threadoffload/guard/ReuseAsyncCompletionStageThreadOffloadGuardTest.java @@ -0,0 +1,52 @@ +package io.smallrye.faulttolerance.reuse.async.completionstage.threadoffload.guard; + +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; +import io.smallrye.faulttolerance.util.WithSystemProperty; + +@FaultToleranceBasicTest +@AddBeanClasses(MyFaultTolerance.class) +public class ReuseAsyncCompletionStageThreadOffloadGuardTest { + @Test + public void test(MyService service) throws ExecutionException, InterruptedException { + testCompatible(service); + } + + @Test + @WithSystemProperty(key = "smallrye.faulttolerance.mp-compatibility", value = "true") + public void testCompatible(MyService service) throws ExecutionException, InterruptedException { + Thread currentThread = Thread.currentThread(); + + // not truly async per `SpecCompatibility`, so thread offload happens per the `Guard` config + assertThat(service.hello().toCompletableFuture().get()).isEqualTo("hello"); + assertThat(MyService.CURRENT_THREAD).doesNotHaveValue(currentThread); + + assertThat(service.helloBlocking().toCompletableFuture().get()).isEqualTo("hello"); + assertThat(MyService.CURRENT_THREAD_BLOCKING).doesNotHaveValue(currentThread); + + assertThat(service.helloNonBlocking().toCompletableFuture().get()).isEqualTo("hello"); + assertThat(MyService.CURRENT_THREAD_NONBLOCKING).hasValue(currentThread); + } + + @Test + @WithSystemProperty(key = "smallrye.faulttolerance.mp-compatibility", value = "false") + public void testNoncompatible(MyService service) throws ExecutionException, InterruptedException { + Thread currentThread = Thread.currentThread(); + + // truly async per `SpecCompatibility` and non-blocking by absence of annotation, no thread offload + assertThat(service.hello().toCompletableFuture().get()).isEqualTo("hello"); + assertThat(MyService.CURRENT_THREAD).hasValue(currentThread); + + assertThat(service.helloBlocking().toCompletableFuture().get()).isEqualTo("hello"); + assertThat(MyService.CURRENT_THREAD_BLOCKING).doesNotHaveValue(currentThread); + + assertThat(service.helloNonBlocking().toCompletableFuture().get()).isEqualTo("hello"); + assertThat(MyService.CURRENT_THREAD_NONBLOCKING).hasValue(currentThread); + } +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/completionstage/threadoffload/typedguard/MyFaultTolerance.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/completionstage/threadoffload/typedguard/MyFaultTolerance.java new file mode 100644 index 00000000..966c0f84 --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/completionstage/threadoffload/typedguard/MyFaultTolerance.java @@ -0,0 +1,20 @@ +package io.smallrye.faulttolerance.reuse.async.completionstage.threadoffload.typedguard; + +import java.util.concurrent.CompletionStage; + +import jakarta.enterprise.inject.Produces; +import jakarta.inject.Singleton; + +import io.smallrye.common.annotation.Identifier; +import io.smallrye.faulttolerance.Types; +import io.smallrye.faulttolerance.api.TypedGuard; + +@Singleton +public class MyFaultTolerance { + // not `static` to create a new instance for each Weld container in the test + @Produces + @Identifier("my-fault-tolerance") + public final TypedGuard> GUARD = TypedGuard.create(Types.CS_STRING) + .withThreadOffload(true) + .build(); +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/completionstage/threadoffload/typedguard/MyService.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/completionstage/threadoffload/typedguard/MyService.java new file mode 100644 index 00000000..d358b600 --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/completionstage/threadoffload/typedguard/MyService.java @@ -0,0 +1,40 @@ +package io.smallrye.faulttolerance.reuse.async.completionstage.threadoffload.typedguard; + +import static java.util.concurrent.CompletableFuture.completedFuture; + +import java.util.concurrent.CompletionStage; +import java.util.concurrent.atomic.AtomicReference; + +import jakarta.enterprise.context.ApplicationScoped; + +import org.eclipse.microprofile.faulttolerance.Asynchronous; + +import io.smallrye.faulttolerance.api.ApplyGuard; +import io.smallrye.faulttolerance.api.AsynchronousNonBlocking; + +@ApplicationScoped +public class MyService { + static final AtomicReference CURRENT_THREAD = new AtomicReference<>(); + static final AtomicReference CURRENT_THREAD_BLOCKING = new AtomicReference<>(); + static final AtomicReference CURRENT_THREAD_NONBLOCKING = new AtomicReference<>(); + + @ApplyGuard("my-fault-tolerance") + public CompletionStage hello() { + CURRENT_THREAD.set(Thread.currentThread()); + return completedFuture("hello"); + } + + @ApplyGuard("my-fault-tolerance") + @Asynchronous + public CompletionStage helloBlocking() { + CURRENT_THREAD_BLOCKING.set(Thread.currentThread()); + return completedFuture("hello"); + } + + @ApplyGuard("my-fault-tolerance") + @AsynchronousNonBlocking + public CompletionStage helloNonBlocking() { + CURRENT_THREAD_NONBLOCKING.set(Thread.currentThread()); + return completedFuture("hello"); + } +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/completionstage/threadoffload/typedguard/ReuseAsyncCompletionStageThreadOffloadTypedGuardTest.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/completionstage/threadoffload/typedguard/ReuseAsyncCompletionStageThreadOffloadTypedGuardTest.java new file mode 100644 index 00000000..4086843a --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/completionstage/threadoffload/typedguard/ReuseAsyncCompletionStageThreadOffloadTypedGuardTest.java @@ -0,0 +1,52 @@ +package io.smallrye.faulttolerance.reuse.async.completionstage.threadoffload.typedguard; + +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; +import io.smallrye.faulttolerance.util.WithSystemProperty; + +@FaultToleranceBasicTest +@AddBeanClasses(MyFaultTolerance.class) +public class ReuseAsyncCompletionStageThreadOffloadTypedGuardTest { + @Test + public void test(MyService service) throws ExecutionException, InterruptedException { + testCompatible(service); + } + + @Test + @WithSystemProperty(key = "smallrye.faulttolerance.mp-compatibility", value = "true") + public void testCompatible(MyService service) throws ExecutionException, InterruptedException { + Thread currentThread = Thread.currentThread(); + + // not truly async per `SpecCompatibility`, so thread offload happens per the `Guard` config + assertThat(service.hello().toCompletableFuture().get()).isEqualTo("hello"); + assertThat(MyService.CURRENT_THREAD).doesNotHaveValue(currentThread); + + assertThat(service.helloBlocking().toCompletableFuture().get()).isEqualTo("hello"); + assertThat(MyService.CURRENT_THREAD_BLOCKING).doesNotHaveValue(currentThread); + + assertThat(service.helloNonBlocking().toCompletableFuture().get()).isEqualTo("hello"); + assertThat(MyService.CURRENT_THREAD_NONBLOCKING).hasValue(currentThread); + } + + @Test + @WithSystemProperty(key = "smallrye.faulttolerance.mp-compatibility", value = "false") + public void testNoncompatible(MyService service) throws ExecutionException, InterruptedException { + Thread currentThread = Thread.currentThread(); + + // truly async per `SpecCompatibility` and non-blocking by absence of annotation, no thread offload + assertThat(service.hello().toCompletableFuture().get()).isEqualTo("hello"); + assertThat(MyService.CURRENT_THREAD).hasValue(currentThread); + + assertThat(service.helloBlocking().toCompletableFuture().get()).isEqualTo("hello"); + assertThat(MyService.CURRENT_THREAD_BLOCKING).doesNotHaveValue(currentThread); + + assertThat(service.helloNonBlocking().toCompletableFuture().get()).isEqualTo("hello"); + assertThat(MyService.CURRENT_THREAD_NONBLOCKING).hasValue(currentThread); + } +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/uni/fallback/guard/MyFaultTolerance.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/uni/fallback/guard/MyFaultTolerance.java new file mode 100644 index 00000000..c65e3cba --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/uni/fallback/guard/MyFaultTolerance.java @@ -0,0 +1,16 @@ +package io.smallrye.faulttolerance.reuse.async.uni.fallback.guard; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.inject.Produces; + +import io.smallrye.common.annotation.Identifier; +import io.smallrye.faulttolerance.api.Guard; + +@ApplicationScoped +public class MyFaultTolerance { + @Produces + @Identifier("my-fault-tolerance") + public static final Guard GUARD = Guard.create() + .withRetry().maxRetries(2).done() + .build(); +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/uni/fallback/guard/MyService.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/uni/fallback/guard/MyService.java new file mode 100644 index 00000000..4fc1a3af --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/uni/fallback/guard/MyService.java @@ -0,0 +1,26 @@ +package io.smallrye.faulttolerance.reuse.async.uni.fallback.guard; + +import java.util.concurrent.atomic.AtomicInteger; + +import jakarta.enterprise.context.ApplicationScoped; + +import org.eclipse.microprofile.faulttolerance.Fallback; + +import io.smallrye.faulttolerance.api.ApplyGuard; +import io.smallrye.mutiny.Uni; + +@ApplicationScoped +public class MyService { + static final AtomicInteger COUNTER = new AtomicInteger(0); + + @ApplyGuard("my-fault-tolerance") + @Fallback(fallbackMethod = "fallback") + public Uni hello() { + COUNTER.incrementAndGet(); + return Uni.createFrom().failure(new IllegalArgumentException()); + } + + Uni fallback() { + return Uni.createFrom().item("better fallback"); + } +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/uni/fallback/guard/ReuseAsyncUniFallbackGuardTest.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/uni/fallback/guard/ReuseAsyncUniFallbackGuardTest.java new file mode 100644 index 00000000..cdb0ca87 --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/uni/fallback/guard/ReuseAsyncUniFallbackGuardTest.java @@ -0,0 +1,20 @@ +package io.smallrye.faulttolerance.reuse.async.uni.fallback.guard; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.time.Duration; + +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 ReuseAsyncUniFallbackGuardTest { + @Test + public void test(MyService service) { + assertThat(service.hello().await().atMost(Duration.ofSeconds(1))).isEqualTo("better fallback"); + assertThat(MyService.COUNTER).hasValue(3); + } +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/uni/fallback/typedguard/MyFaultTolerance.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/uni/fallback/typedguard/MyFaultTolerance.java new file mode 100644 index 00000000..902652aa --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/uni/fallback/typedguard/MyFaultTolerance.java @@ -0,0 +1,19 @@ +package io.smallrye.faulttolerance.reuse.async.uni.fallback.typedguard; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.inject.Produces; + +import io.smallrye.common.annotation.Identifier; +import io.smallrye.faulttolerance.Types; +import io.smallrye.faulttolerance.api.TypedGuard; +import io.smallrye.mutiny.Uni; + +@ApplicationScoped +public class MyFaultTolerance { + @Produces + @Identifier("my-fault-tolerance") + public static final TypedGuard> GUARD = TypedGuard.create(Types.UNI_STRING) + .withRetry().maxRetries(2).done() + .withFallback().handler(() -> Uni.createFrom().item("fallback")).done() + .build(); +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/uni/fallback/typedguard/MyService.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/uni/fallback/typedguard/MyService.java new file mode 100644 index 00000000..6514294e --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/uni/fallback/typedguard/MyService.java @@ -0,0 +1,26 @@ +package io.smallrye.faulttolerance.reuse.async.uni.fallback.typedguard; + +import java.util.concurrent.atomic.AtomicInteger; + +import jakarta.enterprise.context.ApplicationScoped; + +import org.eclipse.microprofile.faulttolerance.Fallback; + +import io.smallrye.faulttolerance.api.ApplyGuard; +import io.smallrye.mutiny.Uni; + +@ApplicationScoped +public class MyService { + static final AtomicInteger COUNTER = new AtomicInteger(0); + + @ApplyGuard("my-fault-tolerance") + @Fallback(fallbackMethod = "fallback") + public Uni hello() { + COUNTER.incrementAndGet(); + return Uni.createFrom().failure(new IllegalArgumentException()); + } + + Uni fallback() { + return Uni.createFrom().item("better fallback"); + } +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/uni/fallback/typedguard/ReuseAsyncUniFallbackTypedGuardTest.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/uni/fallback/typedguard/ReuseAsyncUniFallbackTypedGuardTest.java new file mode 100644 index 00000000..5731d56c --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/uni/fallback/typedguard/ReuseAsyncUniFallbackTypedGuardTest.java @@ -0,0 +1,20 @@ +package io.smallrye.faulttolerance.reuse.async.uni.fallback.typedguard; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.time.Duration; + +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 ReuseAsyncUniFallbackTypedGuardTest { + @Test + public void test(MyService service) { + assertThat(service.hello().await().atMost(Duration.ofSeconds(1))).isEqualTo("better fallback"); + assertThat(MyService.COUNTER).hasValue(3); + } +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/uni/threadoffload/guard/MyFaultTolerance.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/uni/threadoffload/guard/MyFaultTolerance.java new file mode 100644 index 00000000..66a425c4 --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/uni/threadoffload/guard/MyFaultTolerance.java @@ -0,0 +1,17 @@ +package io.smallrye.faulttolerance.reuse.async.uni.threadoffload.guard; + +import jakarta.enterprise.inject.Produces; +import jakarta.inject.Singleton; + +import io.smallrye.common.annotation.Identifier; +import io.smallrye.faulttolerance.api.Guard; + +@Singleton +public class MyFaultTolerance { + // not `static` to create a new instance for each Weld container in the test + @Produces + @Identifier("my-fault-tolerance") + public final Guard GUARD = Guard.create() + .withThreadOffload(true) + .build(); +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/uni/threadoffload/guard/MyService.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/uni/threadoffload/guard/MyService.java new file mode 100644 index 00000000..45c00275 --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/uni/threadoffload/guard/MyService.java @@ -0,0 +1,38 @@ +package io.smallrye.faulttolerance.reuse.async.uni.threadoffload.guard; + +import java.util.concurrent.atomic.AtomicReference; + +import jakarta.enterprise.context.ApplicationScoped; + +import org.eclipse.microprofile.faulttolerance.Asynchronous; + +import io.smallrye.faulttolerance.api.ApplyGuard; +import io.smallrye.faulttolerance.api.AsynchronousNonBlocking; +import io.smallrye.mutiny.Uni; + +@ApplicationScoped +public class MyService { + static final AtomicReference CURRENT_THREAD = new AtomicReference<>(); + static final AtomicReference CURRENT_THREAD_BLOCKING = new AtomicReference<>(); + static final AtomicReference CURRENT_THREAD_NONBLOCKING = new AtomicReference<>(); + + @ApplyGuard("my-fault-tolerance") + public Uni hello() { + CURRENT_THREAD.set(Thread.currentThread()); + return Uni.createFrom().item("hello"); + } + + @ApplyGuard("my-fault-tolerance") + @Asynchronous + public Uni helloBlocking() { + CURRENT_THREAD_BLOCKING.set(Thread.currentThread()); + return Uni.createFrom().item("hello"); + } + + @ApplyGuard("my-fault-tolerance") + @AsynchronousNonBlocking + public Uni helloNonBlocking() { + CURRENT_THREAD_NONBLOCKING.set(Thread.currentThread()); + return Uni.createFrom().item("hello"); + } +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/uni/threadoffload/guard/ReuseAsyncUniThreadOffloadGuardTest.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/uni/threadoffload/guard/ReuseAsyncUniThreadOffloadGuardTest.java new file mode 100644 index 00000000..52d9f1d4 --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/uni/threadoffload/guard/ReuseAsyncUniThreadOffloadGuardTest.java @@ -0,0 +1,52 @@ +package io.smallrye.faulttolerance.reuse.async.uni.threadoffload.guard; + +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; +import io.smallrye.faulttolerance.util.WithSystemProperty; + +@FaultToleranceBasicTest +@AddBeanClasses(MyFaultTolerance.class) +public class ReuseAsyncUniThreadOffloadGuardTest { + @Test + public void test(MyService service) throws ExecutionException, InterruptedException { + testCompatible(service); + } + + @Test + @WithSystemProperty(key = "smallrye.faulttolerance.mp-compatibility", value = "true") + public void testCompatible(MyService service) throws ExecutionException, InterruptedException { + Thread currentThread = Thread.currentThread(); + + // not truly async per `SpecCompatibility`, so thread offload happens per the `Guard` config + assertThat(service.hello().subscribeAsCompletionStage().toCompletableFuture().get()).isEqualTo("hello"); + assertThat(MyService.CURRENT_THREAD).doesNotHaveValue(currentThread); + + assertThat(service.helloBlocking().subscribeAsCompletionStage().toCompletableFuture().get()).isEqualTo("hello"); + assertThat(MyService.CURRENT_THREAD_BLOCKING).doesNotHaveValue(currentThread); + + assertThat(service.helloNonBlocking().subscribeAsCompletionStage().toCompletableFuture().get()).isEqualTo("hello"); + assertThat(MyService.CURRENT_THREAD_NONBLOCKING).hasValue(currentThread); + } + + @Test + @WithSystemProperty(key = "smallrye.faulttolerance.mp-compatibility", value = "false") + public void testNoncompatible(MyService service) throws ExecutionException, InterruptedException { + Thread currentThread = Thread.currentThread(); + + // truly async per `SpecCompatibility` and non-blocking by absence of annotation, no thread offload + assertThat(service.hello().subscribeAsCompletionStage().toCompletableFuture().get()).isEqualTo("hello"); + assertThat(MyService.CURRENT_THREAD).hasValue(currentThread); + + assertThat(service.helloBlocking().subscribeAsCompletionStage().toCompletableFuture().get()).isEqualTo("hello"); + assertThat(MyService.CURRENT_THREAD_BLOCKING).doesNotHaveValue(currentThread); + + assertThat(service.helloNonBlocking().subscribeAsCompletionStage().toCompletableFuture().get()).isEqualTo("hello"); + assertThat(MyService.CURRENT_THREAD_NONBLOCKING).hasValue(currentThread); + } +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/uni/threadoffload/typedguard/MyFaultTolerance.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/uni/threadoffload/typedguard/MyFaultTolerance.java new file mode 100644 index 00000000..f19323ac --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/uni/threadoffload/typedguard/MyFaultTolerance.java @@ -0,0 +1,19 @@ +package io.smallrye.faulttolerance.reuse.async.uni.threadoffload.typedguard; + +import jakarta.enterprise.inject.Produces; +import jakarta.inject.Singleton; + +import io.smallrye.common.annotation.Identifier; +import io.smallrye.faulttolerance.Types; +import io.smallrye.faulttolerance.api.TypedGuard; +import io.smallrye.mutiny.Uni; + +@Singleton +public class MyFaultTolerance { + // not `static` to create a new instance for each Weld container in the test + @Produces + @Identifier("my-fault-tolerance") + public final TypedGuard> GUARD = TypedGuard.create(Types.UNI_STRING) + .withThreadOffload(true) + .build(); +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/uni/threadoffload/typedguard/MyService.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/uni/threadoffload/typedguard/MyService.java new file mode 100644 index 00000000..8d9eea73 --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/uni/threadoffload/typedguard/MyService.java @@ -0,0 +1,38 @@ +package io.smallrye.faulttolerance.reuse.async.uni.threadoffload.typedguard; + +import java.util.concurrent.atomic.AtomicReference; + +import jakarta.enterprise.context.ApplicationScoped; + +import org.eclipse.microprofile.faulttolerance.Asynchronous; + +import io.smallrye.faulttolerance.api.ApplyGuard; +import io.smallrye.faulttolerance.api.AsynchronousNonBlocking; +import io.smallrye.mutiny.Uni; + +@ApplicationScoped +public class MyService { + static final AtomicReference CURRENT_THREAD = new AtomicReference<>(); + static final AtomicReference CURRENT_THREAD_BLOCKING = new AtomicReference<>(); + static final AtomicReference CURRENT_THREAD_NONBLOCKING = new AtomicReference<>(); + + @ApplyGuard("my-fault-tolerance") + public Uni hello() { + CURRENT_THREAD.set(Thread.currentThread()); + return Uni.createFrom().item("hello"); + } + + @ApplyGuard("my-fault-tolerance") + @Asynchronous + public Uni helloBlocking() { + CURRENT_THREAD_BLOCKING.set(Thread.currentThread()); + return Uni.createFrom().item("hello"); + } + + @ApplyGuard("my-fault-tolerance") + @AsynchronousNonBlocking + public Uni helloNonBlocking() { + CURRENT_THREAD_NONBLOCKING.set(Thread.currentThread()); + return Uni.createFrom().item("hello"); + } +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/uni/threadoffload/typedguard/ReuseAsyncUniThreadOffloadTypedGuardTest.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/uni/threadoffload/typedguard/ReuseAsyncUniThreadOffloadTypedGuardTest.java new file mode 100644 index 00000000..2d40f9e6 --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/uni/threadoffload/typedguard/ReuseAsyncUniThreadOffloadTypedGuardTest.java @@ -0,0 +1,52 @@ +package io.smallrye.faulttolerance.reuse.async.uni.threadoffload.typedguard; + +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; +import io.smallrye.faulttolerance.util.WithSystemProperty; + +@FaultToleranceBasicTest +@AddBeanClasses(MyFaultTolerance.class) +public class ReuseAsyncUniThreadOffloadTypedGuardTest { + @Test + public void test(MyService service) throws ExecutionException, InterruptedException { + testCompatible(service); + } + + @Test + @WithSystemProperty(key = "smallrye.faulttolerance.mp-compatibility", value = "true") + public void testCompatible(MyService service) throws ExecutionException, InterruptedException { + Thread currentThread = Thread.currentThread(); + + // not truly async per `SpecCompatibility`, so thread offload happens per the `Guard` config + assertThat(service.hello().subscribeAsCompletionStage().toCompletableFuture().get()).isEqualTo("hello"); + assertThat(MyService.CURRENT_THREAD).doesNotHaveValue(currentThread); + + assertThat(service.helloBlocking().subscribeAsCompletionStage().toCompletableFuture().get()).isEqualTo("hello"); + assertThat(MyService.CURRENT_THREAD_BLOCKING).doesNotHaveValue(currentThread); + + assertThat(service.helloNonBlocking().subscribeAsCompletionStage().toCompletableFuture().get()).isEqualTo("hello"); + assertThat(MyService.CURRENT_THREAD_NONBLOCKING).hasValue(currentThread); + } + + @Test + @WithSystemProperty(key = "smallrye.faulttolerance.mp-compatibility", value = "false") + public void testNoncompatible(MyService service) throws ExecutionException, InterruptedException { + Thread currentThread = Thread.currentThread(); + + // truly async per `SpecCompatibility` and non-blocking by absence of annotation, no thread offload + assertThat(service.hello().subscribeAsCompletionStage().toCompletableFuture().get()).isEqualTo("hello"); + assertThat(MyService.CURRENT_THREAD).hasValue(currentThread); + + assertThat(service.helloBlocking().subscribeAsCompletionStage().toCompletableFuture().get()).isEqualTo("hello"); + assertThat(MyService.CURRENT_THREAD_BLOCKING).doesNotHaveValue(currentThread); + + assertThat(service.helloNonBlocking().subscribeAsCompletionStage().toCompletableFuture().get()).isEqualTo("hello"); + assertThat(MyService.CURRENT_THREAD_NONBLOCKING).hasValue(currentThread); + } +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/all/fallback/MixedReuseAllFallbackTest.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/all/fallback/MixedReuseAllFallbackTest.java new file mode 100644 index 00000000..d8dac688 --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/all/fallback/MixedReuseAllFallbackTest.java @@ -0,0 +1,26 @@ +package io.smallrye.faulttolerance.reuse.mixed.all.fallback; + +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 MixedReuseAllFallbackTest { + @Test + public void test(MyService service) throws ExecutionException, InterruptedException { + assertThat(service.hello()).isEqualTo("hello"); + assertThat(MyService.STRING_COUNTER).hasValue(3); + + assertThat(service.theAnswer().toCompletableFuture().get()).isEqualTo(42); + assertThat(MyService.INT_COUNTER).hasValue(3); + + assertThat(service.badNumber().subscribeAsCompletionStage().toCompletableFuture().get()).isEqualTo(13L); + assertThat(MyService.LONG_COUNTER).hasValue(3); + } +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/all/fallback/MyFaultTolerance.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/all/fallback/MyFaultTolerance.java new file mode 100644 index 00000000..318599f0 --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/all/fallback/MyFaultTolerance.java @@ -0,0 +1,16 @@ +package io.smallrye.faulttolerance.reuse.mixed.all.fallback; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.inject.Produces; + +import io.smallrye.common.annotation.Identifier; +import io.smallrye.faulttolerance.api.Guard; + +@ApplicationScoped +public class MyFaultTolerance { + @Produces + @Identifier("my-fault-tolerance") + public static final Guard GUARD = Guard.create() + .withRetry().maxRetries(2).done() + .build(); +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/all/fallback/MyService.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/all/fallback/MyService.java new file mode 100644 index 00000000..f1d2682b --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/all/fallback/MyService.java @@ -0,0 +1,54 @@ +package io.smallrye.faulttolerance.reuse.mixed.all.fallback; + +import static java.util.concurrent.CompletableFuture.completedFuture; +import static java.util.concurrent.CompletableFuture.failedFuture; + +import java.util.concurrent.CompletionStage; +import java.util.concurrent.atomic.AtomicInteger; + +import jakarta.enterprise.context.ApplicationScoped; + +import org.eclipse.microprofile.faulttolerance.Fallback; + +import io.smallrye.faulttolerance.api.ApplyGuard; +import io.smallrye.mutiny.Uni; + +@ApplicationScoped +public class MyService { + static final AtomicInteger STRING_COUNTER = new AtomicInteger(0); + static final AtomicInteger INT_COUNTER = new AtomicInteger(0); + static final AtomicInteger LONG_COUNTER = new AtomicInteger(0); + + @ApplyGuard("my-fault-tolerance") + @Fallback(fallbackMethod = "helloFallback") + public String hello() { + STRING_COUNTER.incrementAndGet(); + throw new IllegalArgumentException(); + } + + String helloFallback() { + return "hello"; + } + + @ApplyGuard("my-fault-tolerance") + @Fallback(fallbackMethod = "theAnswerFallback") + public CompletionStage theAnswer() { + INT_COUNTER.incrementAndGet(); + return failedFuture(new IllegalArgumentException()); + } + + CompletionStage theAnswerFallback() { + return completedFuture(42); + } + + @ApplyGuard("my-fault-tolerance") + @Fallback(fallbackMethod = "badNumberFallback") + public Uni badNumber() { + LONG_COUNTER.incrementAndGet(); + return Uni.createFrom().failure(new IllegalArgumentException()); + } + + Uni badNumberFallback() { + return Uni.createFrom().item(13L); + } +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/async/both/fallback/MixedReuseAsyncBothFallbackTest.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/async/both/fallback/MixedReuseAsyncBothFallbackTest.java new file mode 100644 index 00000000..9dcff4e5 --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/async/both/fallback/MixedReuseAsyncBothFallbackTest.java @@ -0,0 +1,23 @@ +package io.smallrye.faulttolerance.reuse.mixed.async.both.fallback; + +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 MixedReuseAsyncBothFallbackTest { + @Test + public void test(MyService service) throws ExecutionException, InterruptedException { + assertThat(service.hello().toCompletableFuture().get()).isEqualTo("hello"); + assertThat(MyService.STRING_COUNTER).hasValue(3); + + assertThat(service.theAnswer().subscribeAsCompletionStage().toCompletableFuture().get()).isEqualTo(42); + assertThat(MyService.INT_COUNTER).hasValue(3); + } +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/async/both/fallback/MyFaultTolerance.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/async/both/fallback/MyFaultTolerance.java new file mode 100644 index 00000000..dfcbfb22 --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/async/both/fallback/MyFaultTolerance.java @@ -0,0 +1,16 @@ +package io.smallrye.faulttolerance.reuse.mixed.async.both.fallback; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.inject.Produces; + +import io.smallrye.common.annotation.Identifier; +import io.smallrye.faulttolerance.api.Guard; + +@ApplicationScoped +public class MyFaultTolerance { + @Produces + @Identifier("my-fault-tolerance") + public static final Guard GUARD = Guard.create() + .withRetry().maxRetries(2).done() + .build(); +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/async/both/fallback/MyService.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/async/both/fallback/MyService.java new file mode 100644 index 00000000..144566c7 --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/async/both/fallback/MyService.java @@ -0,0 +1,42 @@ +package io.smallrye.faulttolerance.reuse.mixed.async.both.fallback; + +import static java.util.concurrent.CompletableFuture.completedFuture; +import static java.util.concurrent.CompletableFuture.failedFuture; + +import java.util.concurrent.CompletionStage; +import java.util.concurrent.atomic.AtomicInteger; + +import jakarta.enterprise.context.ApplicationScoped; + +import org.eclipse.microprofile.faulttolerance.Fallback; + +import io.smallrye.faulttolerance.api.ApplyGuard; +import io.smallrye.mutiny.Uni; + +@ApplicationScoped +public class MyService { + static final AtomicInteger STRING_COUNTER = new AtomicInteger(0); + static final AtomicInteger INT_COUNTER = new AtomicInteger(0); + + @ApplyGuard("my-fault-tolerance") + @Fallback(fallbackMethod = "helloFallback") + public CompletionStage hello() { + STRING_COUNTER.incrementAndGet(); + return failedFuture(new IllegalArgumentException()); + } + + CompletionStage helloFallback() { + return completedFuture("hello"); + } + + @ApplyGuard("my-fault-tolerance") + @Fallback(fallbackMethod = "theAnswerFallback") + public Uni theAnswer() { + INT_COUNTER.incrementAndGet(); + return Uni.createFrom().failure(new IllegalArgumentException()); + } + + Uni theAnswerFallback() { + return Uni.createFrom().item(42); + } +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/async/both/threadoffload/MixedReuseAsyncBothThreadOffloadTest.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/async/both/threadoffload/MixedReuseAsyncBothThreadOffloadTest.java new file mode 100644 index 00000000..dc0a04ca --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/async/both/threadoffload/MixedReuseAsyncBothThreadOffloadTest.java @@ -0,0 +1,72 @@ +package io.smallrye.faulttolerance.reuse.mixed.async.both.threadoffload; + +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; +import io.smallrye.faulttolerance.util.WithSystemProperty; + +@FaultToleranceBasicTest +@AddBeanClasses(MyFaultTolerance.class) +public class MixedReuseAsyncBothThreadOffloadTest { + @Test + public void test(MyService service) throws ExecutionException, InterruptedException { + testCompatible(service); + } + + @Test + @WithSystemProperty(key = "smallrye.faulttolerance.mp-compatibility", value = "true") + public void testCompatible(MyService service) throws ExecutionException, InterruptedException { + Thread currentThread = Thread.currentThread(); + + // not truly async per `SpecCompatibility`, so thread offload happens per the `Guard` config + assertThat(service.hello().toCompletableFuture().get()).isEqualTo("hello"); + assertThat(MyService.CURRENT_THREAD_HELLO).doesNotHaveValue(currentThread); + + assertThat(service.helloBlocking().toCompletableFuture().get()).isEqualTo("hello"); + assertThat(MyService.CURRENT_THREAD_HELLO_BLOCKING).doesNotHaveValue(currentThread); + + assertThat(service.helloNonBlocking().toCompletableFuture().get()).isEqualTo("hello"); + assertThat(MyService.CURRENT_THREAD_HELLO_NONBLOCKING).hasValue(currentThread); + + // not truly async per `SpecCompatibility`, so thread offload happens per the `Guard` config + assertThat(service.theAnswer().subscribeAsCompletionStage().toCompletableFuture().get()).isEqualTo(42); + assertThat(MyService.CURRENT_THREAD_THEANSWER).doesNotHaveValue(currentThread); + + assertThat(service.theAnswerBlocking().subscribeAsCompletionStage().toCompletableFuture().get()).isEqualTo(42); + assertThat(MyService.CURRENT_THREAD_THEANSWER_BLOCKING).doesNotHaveValue(currentThread); + + assertThat(service.theAnswerNonBlocking().subscribeAsCompletionStage().toCompletableFuture().get()).isEqualTo(42); + assertThat(MyService.CURRENT_THREAD_THEANSWER_NONBLOCKING).hasValue(currentThread); + } + + @Test + @WithSystemProperty(key = "smallrye.faulttolerance.mp-compatibility", value = "false") + public void testNoncompatible(MyService service) throws ExecutionException, InterruptedException { + Thread currentThread = Thread.currentThread(); + + // truly async per `SpecCompatibility` and non-blocking by absence of annotation, no thread offload + assertThat(service.hello().toCompletableFuture().get()).isEqualTo("hello"); + assertThat(MyService.CURRENT_THREAD_HELLO).hasValue(currentThread); + + assertThat(service.helloBlocking().toCompletableFuture().get()).isEqualTo("hello"); + assertThat(MyService.CURRENT_THREAD_HELLO_BLOCKING).doesNotHaveValue(currentThread); + + assertThat(service.helloNonBlocking().toCompletableFuture().get()).isEqualTo("hello"); + assertThat(MyService.CURRENT_THREAD_HELLO_NONBLOCKING).hasValue(currentThread); + + // truly async per `SpecCompatibility` and non-blocking by absence of annotation, no thread offload + assertThat(service.theAnswer().subscribeAsCompletionStage().toCompletableFuture().get()).isEqualTo(42); + assertThat(MyService.CURRENT_THREAD_THEANSWER).hasValue(currentThread); + + assertThat(service.theAnswerBlocking().subscribeAsCompletionStage().toCompletableFuture().get()).isEqualTo(42); + assertThat(MyService.CURRENT_THREAD_THEANSWER_BLOCKING).doesNotHaveValue(currentThread); + + assertThat(service.theAnswerNonBlocking().subscribeAsCompletionStage().toCompletableFuture().get()).isEqualTo(42); + assertThat(MyService.CURRENT_THREAD_THEANSWER_NONBLOCKING).hasValue(currentThread); + } +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/async/both/threadoffload/MyFaultTolerance.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/async/both/threadoffload/MyFaultTolerance.java new file mode 100644 index 00000000..4b430b2c --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/async/both/threadoffload/MyFaultTolerance.java @@ -0,0 +1,17 @@ +package io.smallrye.faulttolerance.reuse.mixed.async.both.threadoffload; + +import jakarta.enterprise.inject.Produces; +import jakarta.inject.Singleton; + +import io.smallrye.common.annotation.Identifier; +import io.smallrye.faulttolerance.api.Guard; + +@Singleton +public class MyFaultTolerance { + // not `static` to create a new instance for each Weld container in the test + @Produces + @Identifier("my-fault-tolerance") + public final Guard GUARD = Guard.create() + .withThreadOffload(true) + .build(); +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/async/both/threadoffload/MyService.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/async/both/threadoffload/MyService.java new file mode 100644 index 00000000..34534531 --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/async/both/threadoffload/MyService.java @@ -0,0 +1,64 @@ +package io.smallrye.faulttolerance.reuse.mixed.async.both.threadoffload; + +import static java.util.concurrent.CompletableFuture.completedFuture; + +import java.util.concurrent.CompletionStage; +import java.util.concurrent.atomic.AtomicReference; + +import jakarta.enterprise.context.ApplicationScoped; + +import org.eclipse.microprofile.faulttolerance.Asynchronous; + +import io.smallrye.faulttolerance.api.ApplyGuard; +import io.smallrye.faulttolerance.api.AsynchronousNonBlocking; +import io.smallrye.mutiny.Uni; + +@ApplicationScoped +public class MyService { + static final AtomicReference CURRENT_THREAD_HELLO = new AtomicReference<>(); + static final AtomicReference CURRENT_THREAD_HELLO_BLOCKING = new AtomicReference<>(); + static final AtomicReference CURRENT_THREAD_HELLO_NONBLOCKING = new AtomicReference<>(); + static final AtomicReference CURRENT_THREAD_THEANSWER = new AtomicReference<>(); + static final AtomicReference CURRENT_THREAD_THEANSWER_BLOCKING = new AtomicReference<>(); + static final AtomicReference CURRENT_THREAD_THEANSWER_NONBLOCKING = new AtomicReference<>(); + + @ApplyGuard("my-fault-tolerance") + public CompletionStage hello() { + CURRENT_THREAD_HELLO.set(Thread.currentThread()); + return completedFuture("hello"); + } + + @ApplyGuard("my-fault-tolerance") + @Asynchronous + public CompletionStage helloBlocking() { + CURRENT_THREAD_HELLO_BLOCKING.set(Thread.currentThread()); + return completedFuture("hello"); + } + + @ApplyGuard("my-fault-tolerance") + @AsynchronousNonBlocking + public CompletionStage helloNonBlocking() { + CURRENT_THREAD_HELLO_NONBLOCKING.set(Thread.currentThread()); + return completedFuture("hello"); + } + + @ApplyGuard("my-fault-tolerance") + public Uni theAnswer() { + CURRENT_THREAD_THEANSWER.set(Thread.currentThread()); + return Uni.createFrom().item(42); + } + + @ApplyGuard("my-fault-tolerance") + @Asynchronous + public Uni theAnswerBlocking() { + CURRENT_THREAD_THEANSWER_BLOCKING.set(Thread.currentThread()); + return Uni.createFrom().item(42); + } + + @ApplyGuard("my-fault-tolerance") + @AsynchronousNonBlocking + public Uni theAnswerNonBlocking() { + CURRENT_THREAD_THEANSWER_NONBLOCKING.set(Thread.currentThread()); + return Uni.createFrom().item(42); + } +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/async/completionstage/fallback/MixedReuseAsyncCompletionStageFallbackTest.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/async/completionstage/fallback/MixedReuseAsyncCompletionStageFallbackTest.java new file mode 100644 index 00000000..ccd8d2b6 --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/async/completionstage/fallback/MixedReuseAsyncCompletionStageFallbackTest.java @@ -0,0 +1,23 @@ +package io.smallrye.faulttolerance.reuse.mixed.async.completionstage.fallback; + +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 MixedReuseAsyncCompletionStageFallbackTest { + @Test + public void test(MyService service) throws ExecutionException, InterruptedException { + assertThat(service.hello().toCompletableFuture().get()).isEqualTo("hello"); + assertThat(MyService.STRING_COUNTER).hasValue(3); + + assertThat(service.theAnswer().toCompletableFuture().get()).isEqualTo(42); + assertThat(MyService.INT_COUNTER).hasValue(3); + } +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/async/completionstage/fallback/MyFaultTolerance.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/async/completionstage/fallback/MyFaultTolerance.java new file mode 100644 index 00000000..20eedea3 --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/async/completionstage/fallback/MyFaultTolerance.java @@ -0,0 +1,16 @@ +package io.smallrye.faulttolerance.reuse.mixed.async.completionstage.fallback; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.inject.Produces; + +import io.smallrye.common.annotation.Identifier; +import io.smallrye.faulttolerance.api.Guard; + +@ApplicationScoped +public class MyFaultTolerance { + @Produces + @Identifier("my-fault-tolerance") + public static final Guard GUARD = Guard.create() + .withRetry().maxRetries(2).done() + .build(); +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/async/completionstage/fallback/MyService.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/async/completionstage/fallback/MyService.java new file mode 100644 index 00000000..5c107236 --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/async/completionstage/fallback/MyService.java @@ -0,0 +1,41 @@ +package io.smallrye.faulttolerance.reuse.mixed.async.completionstage.fallback; + +import static java.util.concurrent.CompletableFuture.completedFuture; +import static java.util.concurrent.CompletableFuture.failedFuture; + +import java.util.concurrent.CompletionStage; +import java.util.concurrent.atomic.AtomicInteger; + +import jakarta.enterprise.context.ApplicationScoped; + +import org.eclipse.microprofile.faulttolerance.Fallback; + +import io.smallrye.faulttolerance.api.ApplyGuard; + +@ApplicationScoped +public class MyService { + static final AtomicInteger STRING_COUNTER = new AtomicInteger(0); + static final AtomicInteger INT_COUNTER = new AtomicInteger(0); + + @ApplyGuard("my-fault-tolerance") + @Fallback(fallbackMethod = "helloFallback") + public CompletionStage hello() { + STRING_COUNTER.incrementAndGet(); + return failedFuture(new IllegalArgumentException()); + } + + CompletionStage helloFallback() { + return completedFuture("hello"); + } + + @ApplyGuard("my-fault-tolerance") + @Fallback(fallbackMethod = "theAnswerFallback") + public CompletionStage theAnswer() { + INT_COUNTER.incrementAndGet(); + return failedFuture(new IllegalArgumentException()); + } + + CompletionStage theAnswerFallback() { + return completedFuture(42); + } +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/async/completionstage/threadoffload/MixedReuseAsyncCompletionStageThreadOffloadTest.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/async/completionstage/threadoffload/MixedReuseAsyncCompletionStageThreadOffloadTest.java new file mode 100644 index 00000000..7e65141d --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/async/completionstage/threadoffload/MixedReuseAsyncCompletionStageThreadOffloadTest.java @@ -0,0 +1,72 @@ +package io.smallrye.faulttolerance.reuse.mixed.async.completionstage.threadoffload; + +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; +import io.smallrye.faulttolerance.util.WithSystemProperty; + +@FaultToleranceBasicTest +@AddBeanClasses(MyFaultTolerance.class) +public class MixedReuseAsyncCompletionStageThreadOffloadTest { + @Test + public void test(MyService service) throws ExecutionException, InterruptedException { + testCompatible(service); + } + + @Test + @WithSystemProperty(key = "smallrye.faulttolerance.mp-compatibility", value = "true") + public void testCompatible(MyService service) throws ExecutionException, InterruptedException { + Thread currentThread = Thread.currentThread(); + + // not truly async per `SpecCompatibility`, so thread offload happens per the `Guard` config + assertThat(service.hello().toCompletableFuture().get()).isEqualTo("hello"); + assertThat(MyService.CURRENT_THREAD_HELLO).doesNotHaveValue(currentThread); + + assertThat(service.helloBlocking().toCompletableFuture().get()).isEqualTo("hello"); + assertThat(MyService.CURRENT_THREAD_HELLO_BLOCKING).doesNotHaveValue(currentThread); + + assertThat(service.helloNonBlocking().toCompletableFuture().get()).isEqualTo("hello"); + assertThat(MyService.CURRENT_THREAD_HELLO_NONBLOCKING).hasValue(currentThread); + + // not truly async per `SpecCompatibility`, so thread offload happens per the `Guard` config + assertThat(service.theAnswer().toCompletableFuture().get()).isEqualTo(42); + assertThat(MyService.CURRENT_THREAD_THEANSWER).doesNotHaveValue(currentThread); + + assertThat(service.theAnswerBlocking().toCompletableFuture().get()).isEqualTo(42); + assertThat(MyService.CURRENT_THREAD_THEANSWER_BLOCKING).doesNotHaveValue(currentThread); + + assertThat(service.theAnswerNonBlocking().toCompletableFuture().get()).isEqualTo(42); + assertThat(MyService.CURRENT_THREAD_THEANSWER_NONBLOCKING).hasValue(currentThread); + } + + @Test + @WithSystemProperty(key = "smallrye.faulttolerance.mp-compatibility", value = "false") + public void testNoncompatible(MyService service) throws ExecutionException, InterruptedException { + Thread currentThread = Thread.currentThread(); + + // truly async per `SpecCompatibility` and non-blocking by absence of annotation, no thread offload + assertThat(service.hello().toCompletableFuture().get()).isEqualTo("hello"); + assertThat(MyService.CURRENT_THREAD_HELLO).hasValue(currentThread); + + assertThat(service.helloBlocking().toCompletableFuture().get()).isEqualTo("hello"); + assertThat(MyService.CURRENT_THREAD_HELLO_BLOCKING).doesNotHaveValue(currentThread); + + assertThat(service.helloNonBlocking().toCompletableFuture().get()).isEqualTo("hello"); + assertThat(MyService.CURRENT_THREAD_HELLO_NONBLOCKING).hasValue(currentThread); + + // truly async per `SpecCompatibility` and non-blocking by absence of annotation, no thread offload + assertThat(service.theAnswer().toCompletableFuture().get()).isEqualTo(42); + assertThat(MyService.CURRENT_THREAD_THEANSWER).hasValue(currentThread); + + assertThat(service.theAnswerBlocking().toCompletableFuture().get()).isEqualTo(42); + assertThat(MyService.CURRENT_THREAD_THEANSWER_BLOCKING).doesNotHaveValue(currentThread); + + assertThat(service.theAnswerNonBlocking().toCompletableFuture().get()).isEqualTo(42); + assertThat(MyService.CURRENT_THREAD_THEANSWER_NONBLOCKING).hasValue(currentThread); + } +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/async/completionstage/threadoffload/MyFaultTolerance.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/async/completionstage/threadoffload/MyFaultTolerance.java new file mode 100644 index 00000000..6502f0a3 --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/async/completionstage/threadoffload/MyFaultTolerance.java @@ -0,0 +1,17 @@ +package io.smallrye.faulttolerance.reuse.mixed.async.completionstage.threadoffload; + +import jakarta.enterprise.inject.Produces; +import jakarta.inject.Singleton; + +import io.smallrye.common.annotation.Identifier; +import io.smallrye.faulttolerance.api.Guard; + +@Singleton +public class MyFaultTolerance { + // not `static` to create a new instance for each Weld container in the test + @Produces + @Identifier("my-fault-tolerance") + public final Guard GUARD = Guard.create() + .withThreadOffload(true) + .build(); +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/async/completionstage/threadoffload/MyService.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/async/completionstage/threadoffload/MyService.java new file mode 100644 index 00000000..1f67bea3 --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/async/completionstage/threadoffload/MyService.java @@ -0,0 +1,63 @@ +package io.smallrye.faulttolerance.reuse.mixed.async.completionstage.threadoffload; + +import static java.util.concurrent.CompletableFuture.completedFuture; + +import java.util.concurrent.CompletionStage; +import java.util.concurrent.atomic.AtomicReference; + +import jakarta.enterprise.context.ApplicationScoped; + +import org.eclipse.microprofile.faulttolerance.Asynchronous; + +import io.smallrye.faulttolerance.api.ApplyGuard; +import io.smallrye.faulttolerance.api.AsynchronousNonBlocking; + +@ApplicationScoped +public class MyService { + static final AtomicReference CURRENT_THREAD_HELLO = new AtomicReference<>(); + static final AtomicReference CURRENT_THREAD_HELLO_BLOCKING = new AtomicReference<>(); + static final AtomicReference CURRENT_THREAD_HELLO_NONBLOCKING = new AtomicReference<>(); + static final AtomicReference CURRENT_THREAD_THEANSWER = new AtomicReference<>(); + static final AtomicReference CURRENT_THREAD_THEANSWER_BLOCKING = new AtomicReference<>(); + static final AtomicReference CURRENT_THREAD_THEANSWER_NONBLOCKING = new AtomicReference<>(); + + @ApplyGuard("my-fault-tolerance") + public CompletionStage hello() { + CURRENT_THREAD_HELLO.set(Thread.currentThread()); + return completedFuture("hello"); + } + + @ApplyGuard("my-fault-tolerance") + @Asynchronous + public CompletionStage helloBlocking() { + CURRENT_THREAD_HELLO_BLOCKING.set(Thread.currentThread()); + return completedFuture("hello"); + } + + @ApplyGuard("my-fault-tolerance") + @AsynchronousNonBlocking + public CompletionStage helloNonBlocking() { + CURRENT_THREAD_HELLO_NONBLOCKING.set(Thread.currentThread()); + return completedFuture("hello"); + } + + @ApplyGuard("my-fault-tolerance") + public CompletionStage theAnswer() { + CURRENT_THREAD_THEANSWER.set(Thread.currentThread()); + return completedFuture(42); + } + + @ApplyGuard("my-fault-tolerance") + @Asynchronous + public CompletionStage theAnswerBlocking() { + CURRENT_THREAD_THEANSWER_BLOCKING.set(Thread.currentThread()); + return completedFuture(42); + } + + @ApplyGuard("my-fault-tolerance") + @AsynchronousNonBlocking + public CompletionStage theAnswerNonBlocking() { + CURRENT_THREAD_THEANSWER_NONBLOCKING.set(Thread.currentThread()); + return completedFuture(42); + } +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/async/uni/fallback/MixedReuseAsyncUniFallbackTest.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/async/uni/fallback/MixedReuseAsyncUniFallbackTest.java new file mode 100644 index 00000000..ad1c9a64 --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/async/uni/fallback/MixedReuseAsyncUniFallbackTest.java @@ -0,0 +1,23 @@ +package io.smallrye.faulttolerance.reuse.mixed.async.uni.fallback; + +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 MixedReuseAsyncUniFallbackTest { + @Test + public void test(MyService service) throws ExecutionException, InterruptedException { + assertThat(service.hello().await().indefinitely()).isEqualTo("hello"); + assertThat(MyService.STRING_COUNTER).hasValue(3); + + assertThat(service.theAnswer().await().indefinitely()).isEqualTo(42); + assertThat(MyService.INT_COUNTER).hasValue(3); + } +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/async/uni/fallback/MyFaultTolerance.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/async/uni/fallback/MyFaultTolerance.java new file mode 100644 index 00000000..86b800ce --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/async/uni/fallback/MyFaultTolerance.java @@ -0,0 +1,16 @@ +package io.smallrye.faulttolerance.reuse.mixed.async.uni.fallback; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.inject.Produces; + +import io.smallrye.common.annotation.Identifier; +import io.smallrye.faulttolerance.api.Guard; + +@ApplicationScoped +public class MyFaultTolerance { + @Produces + @Identifier("my-fault-tolerance") + public static final Guard GUARD = Guard.create() + .withRetry().maxRetries(2).done() + .build(); +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/async/uni/fallback/MyService.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/async/uni/fallback/MyService.java new file mode 100644 index 00000000..9e91d626 --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/async/uni/fallback/MyService.java @@ -0,0 +1,38 @@ +package io.smallrye.faulttolerance.reuse.mixed.async.uni.fallback; + +import java.util.concurrent.atomic.AtomicInteger; + +import jakarta.enterprise.context.ApplicationScoped; + +import org.eclipse.microprofile.faulttolerance.Fallback; + +import io.smallrye.faulttolerance.api.ApplyGuard; +import io.smallrye.mutiny.Uni; + +@ApplicationScoped +public class MyService { + static final AtomicInteger STRING_COUNTER = new AtomicInteger(0); + static final AtomicInteger INT_COUNTER = new AtomicInteger(0); + + @ApplyGuard("my-fault-tolerance") + @Fallback(fallbackMethod = "helloFallback") + public Uni hello() { + STRING_COUNTER.incrementAndGet(); + return Uni.createFrom().failure(new IllegalArgumentException()); + } + + Uni helloFallback() { + return Uni.createFrom().item("hello"); + } + + @ApplyGuard("my-fault-tolerance") + @Fallback(fallbackMethod = "theAnswerFallback") + public Uni theAnswer() { + INT_COUNTER.incrementAndGet(); + return Uni.createFrom().failure(new IllegalArgumentException()); + } + + Uni theAnswerFallback() { + return Uni.createFrom().item(42); + } +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/async/uni/threadoffload/MixedReuseAsyncUniThreadOffloadTest.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/async/uni/threadoffload/MixedReuseAsyncUniThreadOffloadTest.java new file mode 100644 index 00000000..2074bb29 --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/async/uni/threadoffload/MixedReuseAsyncUniThreadOffloadTest.java @@ -0,0 +1,72 @@ +package io.smallrye.faulttolerance.reuse.mixed.async.uni.threadoffload; + +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; +import io.smallrye.faulttolerance.util.WithSystemProperty; + +@FaultToleranceBasicTest +@AddBeanClasses(MyFaultTolerance.class) +public class MixedReuseAsyncUniThreadOffloadTest { + @Test + public void test(MyService service) throws ExecutionException, InterruptedException { + testCompatible(service); + } + + @Test + @WithSystemProperty(key = "smallrye.faulttolerance.mp-compatibility", value = "true") + public void testCompatible(MyService service) throws ExecutionException, InterruptedException { + Thread currentThread = Thread.currentThread(); + + // not truly async per `SpecCompatibility`, so thread offload happens per the `Guard` config + assertThat(service.hello().subscribeAsCompletionStage().toCompletableFuture().get()).isEqualTo("hello"); + assertThat(MyService.CURRENT_THREAD_HELLO).doesNotHaveValue(currentThread); + + assertThat(service.helloBlocking().subscribeAsCompletionStage().toCompletableFuture().get()).isEqualTo("hello"); + assertThat(MyService.CURRENT_THREAD_HELLO_BLOCKING).doesNotHaveValue(currentThread); + + assertThat(service.helloNonBlocking().subscribeAsCompletionStage().toCompletableFuture().get()).isEqualTo("hello"); + assertThat(MyService.CURRENT_THREAD_HELLO_NONBLOCKING).hasValue(currentThread); + + // not truly async per `SpecCompatibility`, so thread offload happens per the `Guard` config + assertThat(service.theAnswer().subscribeAsCompletionStage().toCompletableFuture().get()).isEqualTo(42); + assertThat(MyService.CURRENT_THREAD_THEANSWER).doesNotHaveValue(currentThread); + + assertThat(service.theAnswerBlocking().subscribeAsCompletionStage().toCompletableFuture().get()).isEqualTo(42); + assertThat(MyService.CURRENT_THREAD_THEANSWER_BLOCKING).doesNotHaveValue(currentThread); + + assertThat(service.theAnswerNonBlocking().subscribeAsCompletionStage().toCompletableFuture().get()).isEqualTo(42); + assertThat(MyService.CURRENT_THREAD_THEANSWER_NONBLOCKING).hasValue(currentThread); + } + + @Test + @WithSystemProperty(key = "smallrye.faulttolerance.mp-compatibility", value = "false") + public void testNoncompatible(MyService service) throws ExecutionException, InterruptedException { + Thread currentThread = Thread.currentThread(); + + // truly async per `SpecCompatibility` and non-blocking by absence of annotation, no thread offload + assertThat(service.hello().subscribeAsCompletionStage().toCompletableFuture().get()).isEqualTo("hello"); + assertThat(MyService.CURRENT_THREAD_HELLO).hasValue(currentThread); + + assertThat(service.helloBlocking().subscribeAsCompletionStage().toCompletableFuture().get()).isEqualTo("hello"); + assertThat(MyService.CURRENT_THREAD_HELLO_BLOCKING).doesNotHaveValue(currentThread); + + assertThat(service.helloNonBlocking().subscribeAsCompletionStage().toCompletableFuture().get()).isEqualTo("hello"); + assertThat(MyService.CURRENT_THREAD_HELLO_NONBLOCKING).hasValue(currentThread); + + // truly async per `SpecCompatibility` and non-blocking by absence of annotation, no thread offload + assertThat(service.theAnswer().subscribeAsCompletionStage().toCompletableFuture().get()).isEqualTo(42); + assertThat(MyService.CURRENT_THREAD_THEANSWER).hasValue(currentThread); + + assertThat(service.theAnswerBlocking().subscribeAsCompletionStage().toCompletableFuture().get()).isEqualTo(42); + assertThat(MyService.CURRENT_THREAD_THEANSWER_BLOCKING).doesNotHaveValue(currentThread); + + assertThat(service.theAnswerNonBlocking().subscribeAsCompletionStage().toCompletableFuture().get()).isEqualTo(42); + assertThat(MyService.CURRENT_THREAD_THEANSWER_NONBLOCKING).hasValue(currentThread); + } +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/async/uni/threadoffload/MyFaultTolerance.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/async/uni/threadoffload/MyFaultTolerance.java new file mode 100644 index 00000000..c062e129 --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/async/uni/threadoffload/MyFaultTolerance.java @@ -0,0 +1,17 @@ +package io.smallrye.faulttolerance.reuse.mixed.async.uni.threadoffload; + +import jakarta.enterprise.inject.Produces; +import jakarta.inject.Singleton; + +import io.smallrye.common.annotation.Identifier; +import io.smallrye.faulttolerance.api.Guard; + +@Singleton +public class MyFaultTolerance { + // not `static` to create a new instance for each Weld container in the test + @Produces + @Identifier("my-fault-tolerance") + public final Guard GUARD = Guard.create() + .withThreadOffload(true) + .build(); +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/async/uni/threadoffload/MyService.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/async/uni/threadoffload/MyService.java new file mode 100644 index 00000000..d18c2dd8 --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/async/uni/threadoffload/MyService.java @@ -0,0 +1,61 @@ +package io.smallrye.faulttolerance.reuse.mixed.async.uni.threadoffload; + +import java.util.concurrent.atomic.AtomicReference; + +import jakarta.enterprise.context.ApplicationScoped; + +import org.eclipse.microprofile.faulttolerance.Asynchronous; + +import io.smallrye.faulttolerance.api.ApplyGuard; +import io.smallrye.faulttolerance.api.AsynchronousNonBlocking; +import io.smallrye.mutiny.Uni; + +@ApplicationScoped +public class MyService { + static final AtomicReference CURRENT_THREAD_HELLO = new AtomicReference<>(); + static final AtomicReference CURRENT_THREAD_HELLO_BLOCKING = new AtomicReference<>(); + static final AtomicReference CURRENT_THREAD_HELLO_NONBLOCKING = new AtomicReference<>(); + static final AtomicReference CURRENT_THREAD_THEANSWER = new AtomicReference<>(); + static final AtomicReference CURRENT_THREAD_THEANSWER_BLOCKING = new AtomicReference<>(); + static final AtomicReference CURRENT_THREAD_THEANSWER_NONBLOCKING = new AtomicReference<>(); + + @ApplyGuard("my-fault-tolerance") + public Uni hello() { + CURRENT_THREAD_HELLO.set(Thread.currentThread()); + return Uni.createFrom().item("hello"); + } + + @ApplyGuard("my-fault-tolerance") + @Asynchronous + public Uni helloBlocking() { + CURRENT_THREAD_HELLO_BLOCKING.set(Thread.currentThread()); + return Uni.createFrom().item("hello"); + } + + @ApplyGuard("my-fault-tolerance") + @AsynchronousNonBlocking + public Uni helloNonBlocking() { + CURRENT_THREAD_HELLO_NONBLOCKING.set(Thread.currentThread()); + return Uni.createFrom().item("hello"); + } + + @ApplyGuard("my-fault-tolerance") + public Uni theAnswer() { + CURRENT_THREAD_THEANSWER.set(Thread.currentThread()); + return Uni.createFrom().item(42); + } + + @ApplyGuard("my-fault-tolerance") + @Asynchronous + public Uni theAnswerBlocking() { + CURRENT_THREAD_THEANSWER_BLOCKING.set(Thread.currentThread()); + return Uni.createFrom().item(42); + } + + @ApplyGuard("my-fault-tolerance") + @AsynchronousNonBlocking + public Uni theAnswerNonBlocking() { + CURRENT_THREAD_THEANSWER_NONBLOCKING.set(Thread.currentThread()); + return Uni.createFrom().item(42); + } +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/sync/fallback/MixedReuseSyncFallbackTest.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/sync/fallback/MixedReuseSyncFallbackTest.java new file mode 100644 index 00000000..58099616 --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/sync/fallback/MixedReuseSyncFallbackTest.java @@ -0,0 +1,21 @@ +package io.smallrye.faulttolerance.reuse.mixed.sync.fallback; + +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 MixedReuseSyncFallbackTest { + @Test + public void test(MyService service) { + assertThat(service.hello()).isEqualTo("hello"); + assertThat(MyService.STRING_COUNTER).hasValue(3); + + assertThat(service.theAnswer()).isEqualTo(42); + assertThat(MyService.INT_COUNTER).hasValue(3); + } +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/sync/fallback/MyFaultTolerance.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/sync/fallback/MyFaultTolerance.java new file mode 100644 index 00000000..e5849552 --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/sync/fallback/MyFaultTolerance.java @@ -0,0 +1,16 @@ +package io.smallrye.faulttolerance.reuse.mixed.sync.fallback; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.inject.Produces; + +import io.smallrye.common.annotation.Identifier; +import io.smallrye.faulttolerance.api.Guard; + +@ApplicationScoped +public class MyFaultTolerance { + @Produces + @Identifier("my-fault-tolerance") + public static final Guard GUARD = Guard.create() + .withRetry().maxRetries(2).done() + .build(); +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/sync/fallback/MyService.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/sync/fallback/MyService.java new file mode 100644 index 00000000..14b759b5 --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/sync/fallback/MyService.java @@ -0,0 +1,37 @@ +package io.smallrye.faulttolerance.reuse.mixed.sync.fallback; + +import java.util.concurrent.atomic.AtomicInteger; + +import jakarta.enterprise.context.ApplicationScoped; + +import org.eclipse.microprofile.faulttolerance.Fallback; + +import io.smallrye.faulttolerance.api.ApplyGuard; + +@ApplicationScoped +public class MyService { + static final AtomicInteger STRING_COUNTER = new AtomicInteger(0); + static final AtomicInteger INT_COUNTER = new AtomicInteger(0); + + @ApplyGuard("my-fault-tolerance") + @Fallback(fallbackMethod = "helloFallback") + public String hello() { + STRING_COUNTER.incrementAndGet(); + throw new IllegalArgumentException(); + } + + String helloFallback() { + return "hello"; + } + + @ApplyGuard("my-fault-tolerance") + @Fallback(fallbackMethod = "theAnswerFallback") + public int theAnswer() { + INT_COUNTER.incrementAndGet(); + throw new IllegalArgumentException(); + } + + int theAnswerFallback() { + return 42; + } +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/sync/threadoffload/MixedReuseSyncThreadOffloadTest.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/sync/threadoffload/MixedReuseSyncThreadOffloadTest.java new file mode 100644 index 00000000..d022410e --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/sync/threadoffload/MixedReuseSyncThreadOffloadTest.java @@ -0,0 +1,23 @@ +package io.smallrye.faulttolerance.reuse.mixed.sync.threadoffload; + +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 MixedReuseSyncThreadOffloadTest { + @Test + public void test(MyService service) { + Thread currentThread = Thread.currentThread(); + + assertThat(service.hello()).isEqualTo("hello"); + assertThat(MyService.CURRENT_THREAD_STRING).hasValue(currentThread); + + assertThat(service.theAnswer()).isEqualTo(42); + assertThat(MyService.CURRENT_THREAD_INT).hasValue(currentThread); + } +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/sync/threadoffload/MyFaultTolerance.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/sync/threadoffload/MyFaultTolerance.java new file mode 100644 index 00000000..bb407bf1 --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/sync/threadoffload/MyFaultTolerance.java @@ -0,0 +1,16 @@ +package io.smallrye.faulttolerance.reuse.mixed.sync.threadoffload; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.inject.Produces; + +import io.smallrye.common.annotation.Identifier; +import io.smallrye.faulttolerance.api.Guard; + +@ApplicationScoped +public class MyFaultTolerance { + @Produces + @Identifier("my-fault-tolerance") + public static final Guard GUARD = Guard.create() + .withThreadOffload(true) + .build(); +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/sync/threadoffload/MyService.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/sync/threadoffload/MyService.java new file mode 100644 index 00000000..df6a2b08 --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/mixed/sync/threadoffload/MyService.java @@ -0,0 +1,25 @@ +package io.smallrye.faulttolerance.reuse.mixed.sync.threadoffload; + +import java.util.concurrent.atomic.AtomicReference; + +import jakarta.enterprise.context.ApplicationScoped; + +import io.smallrye.faulttolerance.api.ApplyGuard; + +@ApplicationScoped +public class MyService { + static final AtomicReference CURRENT_THREAD_STRING = new AtomicReference<>(); + static final AtomicReference CURRENT_THREAD_INT = new AtomicReference<>(); + + @ApplyGuard("my-fault-tolerance") + public String hello() { + CURRENT_THREAD_STRING.set(Thread.currentThread()); + return "hello"; + } + + @ApplyGuard("my-fault-tolerance") + public int theAnswer() { + CURRENT_THREAD_INT.set(Thread.currentThread()); + return 42; + } +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/sync/fallback/guard/MyFaultTolerance.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/sync/fallback/guard/MyFaultTolerance.java new file mode 100644 index 00000000..85a9ba82 --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/sync/fallback/guard/MyFaultTolerance.java @@ -0,0 +1,16 @@ +package io.smallrye.faulttolerance.reuse.sync.fallback.guard; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.inject.Produces; + +import io.smallrye.common.annotation.Identifier; +import io.smallrye.faulttolerance.api.Guard; + +@ApplicationScoped +public class MyFaultTolerance { + @Produces + @Identifier("my-fault-tolerance") + public static final Guard GUARD = Guard.create() + .withRetry().maxRetries(2).done() + .build(); +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/sync/fallback/guard/MyService.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/sync/fallback/guard/MyService.java new file mode 100644 index 00000000..9e026859 --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/sync/fallback/guard/MyService.java @@ -0,0 +1,25 @@ +package io.smallrye.faulttolerance.reuse.sync.fallback.guard; + +import java.util.concurrent.atomic.AtomicInteger; + +import jakarta.enterprise.context.ApplicationScoped; + +import org.eclipse.microprofile.faulttolerance.Fallback; + +import io.smallrye.faulttolerance.api.ApplyGuard; + +@ApplicationScoped +public class MyService { + static final AtomicInteger COUNTER = new AtomicInteger(0); + + @ApplyGuard("my-fault-tolerance") + @Fallback(fallbackMethod = "fallback") + public String hello() { + COUNTER.incrementAndGet(); + throw new IllegalArgumentException(); + } + + String fallback() { + return "better fallback"; + } +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/sync/fallback/guard/ReuseSyncFallbackGuardTest.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/sync/fallback/guard/ReuseSyncFallbackGuardTest.java new file mode 100644 index 00000000..89f4bd76 --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/sync/fallback/guard/ReuseSyncFallbackGuardTest.java @@ -0,0 +1,18 @@ +package io.smallrye.faulttolerance.reuse.sync.fallback.guard; + +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 ReuseSyncFallbackGuardTest { + @Test + public void test(MyService service) { + assertThat(service.hello()).isEqualTo("better fallback"); + assertThat(MyService.COUNTER).hasValue(3); + } +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/sync/fallback/typedguard/MyFaultTolerance.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/sync/fallback/typedguard/MyFaultTolerance.java new file mode 100644 index 00000000..8448695b --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/sync/fallback/typedguard/MyFaultTolerance.java @@ -0,0 +1,17 @@ +package io.smallrye.faulttolerance.reuse.sync.fallback.typedguard; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.inject.Produces; + +import io.smallrye.common.annotation.Identifier; +import io.smallrye.faulttolerance.api.TypedGuard; + +@ApplicationScoped +public class MyFaultTolerance { + @Produces + @Identifier("my-fault-tolerance") + public static final TypedGuard GUARD = TypedGuard.create(String.class) + .withRetry().maxRetries(2).done() + .withFallback().handler(() -> "fallback").done() + .build(); +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/sync/fallback/typedguard/MyService.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/sync/fallback/typedguard/MyService.java new file mode 100644 index 00000000..0f33bced --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/sync/fallback/typedguard/MyService.java @@ -0,0 +1,25 @@ +package io.smallrye.faulttolerance.reuse.sync.fallback.typedguard; + +import java.util.concurrent.atomic.AtomicInteger; + +import jakarta.enterprise.context.ApplicationScoped; + +import org.eclipse.microprofile.faulttolerance.Fallback; + +import io.smallrye.faulttolerance.api.ApplyGuard; + +@ApplicationScoped +public class MyService { + static final AtomicInteger COUNTER = new AtomicInteger(0); + + @ApplyGuard("my-fault-tolerance") + @Fallback(fallbackMethod = "fallback") + public String hello() { + COUNTER.incrementAndGet(); + throw new IllegalArgumentException(); + } + + String fallback() { + return "better fallback"; + } +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/sync/fallback/typedguard/ReuseSyncFallbackTypedGuardTest.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/sync/fallback/typedguard/ReuseSyncFallbackTypedGuardTest.java new file mode 100644 index 00000000..08c7105b --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/sync/fallback/typedguard/ReuseSyncFallbackTypedGuardTest.java @@ -0,0 +1,18 @@ +package io.smallrye.faulttolerance.reuse.sync.fallback.typedguard; + +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 ReuseSyncFallbackTypedGuardTest { + @Test + public void test(MyService service) { + assertThat(service.hello()).isEqualTo("better fallback"); + assertThat(MyService.COUNTER).hasValue(3); + } +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/sync/threadoffload/guard/MyFaultTolerance.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/sync/threadoffload/guard/MyFaultTolerance.java new file mode 100644 index 00000000..ff16f016 --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/sync/threadoffload/guard/MyFaultTolerance.java @@ -0,0 +1,16 @@ +package io.smallrye.faulttolerance.reuse.sync.threadoffload.guard; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.inject.Produces; + +import io.smallrye.common.annotation.Identifier; +import io.smallrye.faulttolerance.api.Guard; + +@ApplicationScoped +public class MyFaultTolerance { + @Produces + @Identifier("my-fault-tolerance") + public static final Guard GUARD = Guard.create() + .withThreadOffload(true) + .build(); +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/sync/threadoffload/guard/MyService.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/sync/threadoffload/guard/MyService.java new file mode 100644 index 00000000..257e9dce --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/sync/threadoffload/guard/MyService.java @@ -0,0 +1,18 @@ +package io.smallrye.faulttolerance.reuse.sync.threadoffload.guard; + +import java.util.concurrent.atomic.AtomicReference; + +import jakarta.enterprise.context.ApplicationScoped; + +import io.smallrye.faulttolerance.api.ApplyGuard; + +@ApplicationScoped +public class MyService { + static final AtomicReference CURRENT_THREAD = new AtomicReference<>(); + + @ApplyGuard("my-fault-tolerance") + public String hello() { + CURRENT_THREAD.set(Thread.currentThread()); + return "hello"; + } +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/sync/threadoffload/guard/ReuseSyncThreadOffloadGuardTest.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/sync/threadoffload/guard/ReuseSyncThreadOffloadGuardTest.java new file mode 100644 index 00000000..224d380c --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/sync/threadoffload/guard/ReuseSyncThreadOffloadGuardTest.java @@ -0,0 +1,20 @@ +package io.smallrye.faulttolerance.reuse.sync.threadoffload.guard; + +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 ReuseSyncThreadOffloadGuardTest { + @Test + public void test(MyService service) { + Thread currentThread = Thread.currentThread(); + + assertThat(service.hello()).isEqualTo("hello"); + assertThat(MyService.CURRENT_THREAD).hasValue(currentThread); + } +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/sync/threadoffload/typedguard/MyFaultTolerance.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/sync/threadoffload/typedguard/MyFaultTolerance.java new file mode 100644 index 00000000..eeb56f5a --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/sync/threadoffload/typedguard/MyFaultTolerance.java @@ -0,0 +1,16 @@ +package io.smallrye.faulttolerance.reuse.sync.threadoffload.typedguard; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.inject.Produces; + +import io.smallrye.common.annotation.Identifier; +import io.smallrye.faulttolerance.api.TypedGuard; + +@ApplicationScoped +public class MyFaultTolerance { + @Produces + @Identifier("my-fault-tolerance") + public static final TypedGuard GUARD = TypedGuard.create(String.class) + .withThreadOffload(true) + .build(); +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/sync/threadoffload/typedguard/MyService.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/sync/threadoffload/typedguard/MyService.java new file mode 100644 index 00000000..e7fde2a5 --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/sync/threadoffload/typedguard/MyService.java @@ -0,0 +1,18 @@ +package io.smallrye.faulttolerance.reuse.sync.threadoffload.typedguard; + +import java.util.concurrent.atomic.AtomicReference; + +import jakarta.enterprise.context.ApplicationScoped; + +import io.smallrye.faulttolerance.api.ApplyGuard; + +@ApplicationScoped +public class MyService { + static final AtomicReference CURRENT_THREAD = new AtomicReference<>(); + + @ApplyGuard("my-fault-tolerance") + public String hello() { + CURRENT_THREAD.set(Thread.currentThread()); + return "hello"; + } +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/sync/threadoffload/typedguard/ReuseSyncThreadOffloadTypedGuardTest.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/sync/threadoffload/typedguard/ReuseSyncThreadOffloadTypedGuardTest.java new file mode 100644 index 00000000..b5073874 --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/sync/threadoffload/typedguard/ReuseSyncThreadOffloadTypedGuardTest.java @@ -0,0 +1,20 @@ +package io.smallrye.faulttolerance.reuse.sync.threadoffload.typedguard; + +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 ReuseSyncThreadOffloadTypedGuardTest { + @Test + public void test(MyService service) { + Thread currentThread = Thread.currentThread(); + + assertThat(service.hello()).isEqualTo("hello"); + assertThat(MyService.CURRENT_THREAD).hasValue(currentThread); + } +} diff --git a/testsuite/basic/src/test/kotlin/io/smallrye/faulttolerance/kotlin/reuse/fallback/guard/MyFaultTolerance.kt b/testsuite/basic/src/test/kotlin/io/smallrye/faulttolerance/kotlin/reuse/fallback/guard/MyFaultTolerance.kt new file mode 100644 index 00000000..57fe1424 --- /dev/null +++ b/testsuite/basic/src/test/kotlin/io/smallrye/faulttolerance/kotlin/reuse/fallback/guard/MyFaultTolerance.kt @@ -0,0 +1,15 @@ +package io.smallrye.faulttolerance.kotlin.reuse.fallback.guard + +import io.smallrye.common.annotation.Identifier +import io.smallrye.faulttolerance.api.Guard +import jakarta.enterprise.context.ApplicationScoped +import jakarta.enterprise.inject.Produces + +@ApplicationScoped +object MyFaultTolerance { + @Produces + @Identifier("my-fault-tolerance") + val GUARD = Guard.create() + .withRetry().maxRetries(2).done() + .build() +} diff --git a/testsuite/basic/src/test/kotlin/io/smallrye/faulttolerance/kotlin/reuse/fallback/guard/MyService.kt b/testsuite/basic/src/test/kotlin/io/smallrye/faulttolerance/kotlin/reuse/fallback/guard/MyService.kt new file mode 100644 index 00000000..c9b2f16f --- /dev/null +++ b/testsuite/basic/src/test/kotlin/io/smallrye/faulttolerance/kotlin/reuse/fallback/guard/MyService.kt @@ -0,0 +1,22 @@ +package io.smallrye.faulttolerance.kotlin.reuse.fallback.guard + +import io.smallrye.faulttolerance.api.ApplyGuard +import jakarta.enterprise.context.ApplicationScoped +import org.eclipse.microprofile.faulttolerance.Fallback +import java.util.concurrent.atomic.AtomicInteger + +@ApplicationScoped +open class MyService { + companion object { + val COUNTER = AtomicInteger(0) + } + + @ApplyGuard("my-fault-tolerance") + @Fallback(fallbackMethod = "fallback") + open fun hello(): String { + COUNTER.incrementAndGet() + throw IllegalArgumentException() + } + + private fun fallback() = "better fallback" +} diff --git a/testsuite/basic/src/test/kotlin/io/smallrye/faulttolerance/kotlin/reuse/fallback/guard/ReuseKotlinFallbackGuardTest.kt b/testsuite/basic/src/test/kotlin/io/smallrye/faulttolerance/kotlin/reuse/fallback/guard/ReuseKotlinFallbackGuardTest.kt new file mode 100644 index 00000000..a2fd2b0d --- /dev/null +++ b/testsuite/basic/src/test/kotlin/io/smallrye/faulttolerance/kotlin/reuse/fallback/guard/ReuseKotlinFallbackGuardTest.kt @@ -0,0 +1,16 @@ +package io.smallrye.faulttolerance.kotlin.reuse.fallback.guard + +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 ReuseKotlinFallbackGuardTest { + @Test + fun test(service: MyService) { + assertThat(service.hello()).isEqualTo("better fallback") + assertThat(MyService.COUNTER).hasValue(3) + } +} diff --git a/testsuite/basic/src/test/kotlin/io/smallrye/faulttolerance/kotlin/reuse/fallback/typedguard/MyFaultTolerance.kt b/testsuite/basic/src/test/kotlin/io/smallrye/faulttolerance/kotlin/reuse/fallback/typedguard/MyFaultTolerance.kt new file mode 100644 index 00000000..72544442 --- /dev/null +++ b/testsuite/basic/src/test/kotlin/io/smallrye/faulttolerance/kotlin/reuse/fallback/typedguard/MyFaultTolerance.kt @@ -0,0 +1,17 @@ +package io.smallrye.faulttolerance.kotlin.reuse.fallback.typedguard + +import io.smallrye.common.annotation.Identifier +import io.smallrye.faulttolerance.api.TypedGuard +import jakarta.enterprise.context.ApplicationScoped +import jakarta.enterprise.inject.Produces +import java.util.function.Supplier + +@ApplicationScoped +object MyFaultTolerance { + @Produces + @Identifier("my-fault-tolerance") + val GUARD = TypedGuard.create(String::class.java) + .withRetry().maxRetries(2).done() + .withFallback().handler(Supplier { "fallback" }).done() + .build() +} diff --git a/testsuite/basic/src/test/kotlin/io/smallrye/faulttolerance/kotlin/reuse/fallback/typedguard/MyService.kt b/testsuite/basic/src/test/kotlin/io/smallrye/faulttolerance/kotlin/reuse/fallback/typedguard/MyService.kt new file mode 100644 index 00000000..c152025d --- /dev/null +++ b/testsuite/basic/src/test/kotlin/io/smallrye/faulttolerance/kotlin/reuse/fallback/typedguard/MyService.kt @@ -0,0 +1,22 @@ +package io.smallrye.faulttolerance.kotlin.reuse.fallback.typedguard + +import io.smallrye.faulttolerance.api.ApplyGuard +import jakarta.enterprise.context.ApplicationScoped +import org.eclipse.microprofile.faulttolerance.Fallback +import java.util.concurrent.atomic.AtomicInteger + +@ApplicationScoped +open class MyService { + companion object { + val COUNTER = AtomicInteger(0) + } + + @ApplyGuard("my-fault-tolerance") + @Fallback(fallbackMethod = "fallback") + open fun hello(): String { + COUNTER.incrementAndGet() + throw IllegalArgumentException() + } + + private fun fallback() = "better fallback" +} diff --git a/testsuite/basic/src/test/kotlin/io/smallrye/faulttolerance/kotlin/reuse/fallback/typedguard/ReuseKotlinFallbackTypedGuardTest.kt b/testsuite/basic/src/test/kotlin/io/smallrye/faulttolerance/kotlin/reuse/fallback/typedguard/ReuseKotlinFallbackTypedGuardTest.kt new file mode 100644 index 00000000..b914a7c1 --- /dev/null +++ b/testsuite/basic/src/test/kotlin/io/smallrye/faulttolerance/kotlin/reuse/fallback/typedguard/ReuseKotlinFallbackTypedGuardTest.kt @@ -0,0 +1,16 @@ +package io.smallrye.faulttolerance.kotlin.reuse.fallback.typedguard + +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 ReuseKotlinFallbackTypedGuardTest { + @Test + fun test(service: MyService) { + assertThat(service.hello()).isEqualTo("better fallback") + assertThat(MyService.COUNTER).hasValue(3) + } +}