From 9872370ee0a642ce2879e7a6192c213b6b9eca84 Mon Sep 17 00:00:00 2001 From: Jan Bernitt Date: Fri, 29 Mar 2019 17:13:43 +0100 Subject: [PATCH 01/30] PAYARA-3468 fixed: consider global override --- .../microprofile/faulttolerance/cdi/FaultToleranceCdiUtils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/cdi/FaultToleranceCdiUtils.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/cdi/FaultToleranceCdiUtils.java index 1b58a0eaa76..9ca0247652a 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/cdi/FaultToleranceCdiUtils.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/cdi/FaultToleranceCdiUtils.java @@ -292,7 +292,7 @@ private static Optional checkAnnotatedMethodForEnabledOverride(String a Boolean.class, config); if (!value.isPresent()) { - checkForGlobalLevelOverride(annotationName, "enabled", Boolean.class, config); + value = checkForGlobalLevelOverride(annotationName, "enabled", Boolean.class, config); } return value; From 5c48af888c4e5b2e7c797b0e5155ed63010a1791 Mon Sep 17 00:00:00 2001 From: Jan Bernitt Date: Mon, 1 Apr 2019 16:54:45 +0200 Subject: [PATCH 02/30] PAYARA-3468 update to API and TCK 2.0 --- .../faulttolerance/interceptors/TimeoutInterceptor.java | 8 ++++---- .../interceptors/fallback/FallbackPolicy.java | 2 +- appserver/pom.xml | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/TimeoutInterceptor.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/TimeoutInterceptor.java index 6541f27cb7c..e2204da71c5 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/TimeoutInterceptor.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/TimeoutInterceptor.java @@ -68,6 +68,7 @@ import org.eclipse.microprofile.faulttolerance.exceptions.TimeoutException; import org.eclipse.microprofile.metrics.MetricRegistry; import org.glassfish.api.invocation.InvocationManager; +import org.glassfish.hk2.api.ServiceLocator; import org.glassfish.internal.api.Globals; /** @@ -88,10 +89,9 @@ public class TimeoutInterceptor { public Object intercept(InvocationContext invocationContext) throws Exception { Object proceededInvocationContext = null; - FaultToleranceService faultToleranceService = - Globals.getDefaultBaseServiceLocator().getService(FaultToleranceService.class); - InvocationManager invocationManager = Globals.getDefaultBaseServiceLocator() - .getService(InvocationManager.class); + ServiceLocator serviceLocator = Globals.getDefaultBaseServiceLocator(); + FaultToleranceService faultToleranceService = serviceLocator.getService(FaultToleranceService.class); + InvocationManager invocationManager = serviceLocator.getService(InvocationManager.class); MetricRegistry metricRegistry = CDI.current().select(MetricRegistry.class).get(); String fullMethodSignature = FaultToleranceCdiUtils.getFullAnnotatedMethodSignature(invocationContext, diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/fallback/FallbackPolicy.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/fallback/FallbackPolicy.java index 6d68bfddbca..ed1c16681bf 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/fallback/FallbackPolicy.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/fallback/FallbackPolicy.java @@ -60,7 +60,7 @@ import org.glassfish.internal.api.Globals; /** - * Class that executes the fallback policy defined by the @Fallback annotation. + * Class that executes the fallback policy defined by the {@link Fallback} annotation. * @author Andrew Pielage */ public class FallbackPolicy { diff --git a/appserver/pom.xml b/appserver/pom.xml index 8643c33d868..bfbce0e091c 100644 --- a/appserver/pom.xml +++ b/appserver/pom.xml @@ -201,7 +201,7 @@ 1.0.0-Beta - 1.1.1 + 2.0 1.1.payara-p1 1.0.payara-p1 1.1.payara-p1 From fdda0965908774cc43e8d58b1568a53970539de9 Mon Sep 17 00:00:00 2001 From: Jan Bernitt Date: Mon, 8 Apr 2019 14:37:21 +0200 Subject: [PATCH 03/30] PAYARA-3468 decoupling, merge to single interceptor, added policies for FT flow control --- ...va => FaultToleranceApplicationState.java} | 34 +- .../faulttolerance/FaultToleranceConfig.java | 169 ++++++++ .../FaultToleranceExecution.java | 32 ++ .../faulttolerance/FaultToleranceMetrics.java | 176 ++++++++ .../faulttolerance/FaultToleranceService.java | 406 +++++++++--------- .../cdi/CdiFaultToleranceConfig.java | 273 ++++++++++++ .../cdi/CdiFaultToleranceMetrics.java | 61 +++ .../cdi/FaultToleranceCDIExtension.java | 114 ++--- .../cdi/FaultToleranceCdiUtils.java | 183 ++++---- .../interceptors/AsynchronousInterceptor.java | 127 +++--- .../BaseFaultToleranceInterceptor.java | 97 +++++ .../interceptors/BulkheadInterceptor.java | 326 +++++--------- .../CircuitBreakerInterceptor.java | 268 +++--------- .../FaultToleranceInterceptor.java | 59 +++ .../interceptors/RetryInterceptor.java | 254 +++-------- .../interceptors/TimeoutInterceptor.java | 202 +++------ .../interceptors/fallback/FallbackPolicy.java | 103 ++--- .../model/AsynchronousPolicy.java | 39 ++ .../faulttolerance/model/BulkheadPolicy.java | 35 ++ .../model/CircuitBreakerPolicy.java | 52 +++ .../faulttolerance/model/FallbackPolicy.java | 50 +++ .../model/FaultToleranceBehaviour.java | 32 ++ .../model/FaultTolerancePolicy.java | 122 ++++++ .../faulttolerance/model/Policy.java | 66 +++ .../faulttolerance/model/RetryPolicy.java | 63 +++ .../faulttolerance/model/TimeoutPolicy.java | 35 ++ .../state/BulkheadSemaphore.java | 27 ++ .../state/CircuitBreakerState.java | 16 +- .../validators/AsynchronousValidator.java | 9 +- .../validators/FallbackValidator.java | 4 +- 30 files changed, 2080 insertions(+), 1354 deletions(-) rename appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/{FaultToleranceObject.java => FaultToleranceApplicationState.java} (72%) create mode 100644 appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/FaultToleranceConfig.java create mode 100644 appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/FaultToleranceExecution.java create mode 100644 appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/FaultToleranceMetrics.java create mode 100644 appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/cdi/CdiFaultToleranceConfig.java create mode 100644 appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/cdi/CdiFaultToleranceMetrics.java create mode 100644 appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/BaseFaultToleranceInterceptor.java create mode 100644 appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/FaultToleranceInterceptor.java create mode 100644 appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/model/AsynchronousPolicy.java create mode 100644 appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/model/BulkheadPolicy.java create mode 100644 appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/model/CircuitBreakerPolicy.java create mode 100644 appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/model/FallbackPolicy.java create mode 100644 appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/model/FaultToleranceBehaviour.java create mode 100644 appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/model/FaultTolerancePolicy.java create mode 100644 appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/model/Policy.java create mode 100644 appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/model/RetryPolicy.java create mode 100644 appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/model/TimeoutPolicy.java create mode 100644 appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/state/BulkheadSemaphore.java diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/FaultToleranceObject.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/FaultToleranceApplicationState.java similarity index 72% rename from appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/FaultToleranceObject.java rename to appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/FaultToleranceApplicationState.java index dc85115d19a..ce6c5d4c7a0 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/FaultToleranceObject.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/FaultToleranceApplicationState.java @@ -39,49 +39,31 @@ */ package fish.payara.microprofile.faulttolerance; +import fish.payara.microprofile.faulttolerance.state.BulkheadSemaphore; import fish.payara.microprofile.faulttolerance.state.CircuitBreakerState; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.Semaphore; /** * * @author Andrew Pielage andrew.pielage@payara.fish */ -public class FaultToleranceObject { - - private final boolean enabled; - private final boolean metricsEnabled; - private final Map> circuitBreakerStates; - private final Map> bulkheadExecutionSemaphores; - private final Map> bulkheadExecutionQueueSemaphores; - - public FaultToleranceObject(Boolean enabled, Boolean metricsEnabled) { - this.enabled = enabled; - this.metricsEnabled = metricsEnabled; - circuitBreakerStates = new ConcurrentHashMap<>(); - bulkheadExecutionSemaphores = new ConcurrentHashMap<>(); - bulkheadExecutionQueueSemaphores = new ConcurrentHashMap<>(); - } +public class FaultToleranceApplicationState { - public boolean isEnabled() { - return enabled; - } - - public boolean areMetricsEnabled() { - return metricsEnabled; - } + private final Map> circuitBreakerStates = new ConcurrentHashMap<>(); + private final Map> bulkheadExecutionSemaphores = new ConcurrentHashMap<>(); + private final Map> bulkheadExecutionQueueSemaphores = new ConcurrentHashMap<>(); public Map> getCircuitBreakerStates() { return circuitBreakerStates; } - public Map> getBulkheadExecutionSemaphores() { + public Map> getBulkheadExecutionSemaphores() { return bulkheadExecutionSemaphores; } - public Map> getBulkheadExecutionQueueSemaphores() { + public Map> getBulkheadExecutionQueueSemaphores() { return bulkheadExecutionQueueSemaphores; } - + } diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/FaultToleranceConfig.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/FaultToleranceConfig.java new file mode 100644 index 00000000000..82b928f7a5e --- /dev/null +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/FaultToleranceConfig.java @@ -0,0 +1,169 @@ +package fish.payara.microprofile.faulttolerance; + +import java.lang.annotation.Annotation; +import java.time.temporal.ChronoUnit; + +import javax.enterprise.inject.spi.BeanManager; +import javax.interceptor.InvocationContext; + +import org.eclipse.microprofile.faulttolerance.Bulkhead; +import org.eclipse.microprofile.faulttolerance.CircuitBreaker; +import org.eclipse.microprofile.faulttolerance.Fallback; +import org.eclipse.microprofile.faulttolerance.FallbackHandler; +import org.eclipse.microprofile.faulttolerance.Retry; +import org.eclipse.microprofile.faulttolerance.Timeout; +import org.jvnet.hk2.annotations.Contract; + +import fish.payara.microprofile.faulttolerance.state.CircuitBreakerState; + +/** + * Encapsulates all properties extracted from FT annotations and the {@link org.eclipse.microprofile.config.Config} so + * that the processing can be declared independent of the actual resolution mechanism. + * + * The default implementations provided will extract properties plain from the given annotations. + */ +@SuppressWarnings("unused") +@Contract +public interface FaultToleranceConfig { + + /** + * FT behaves as stated by the present FT annotations. + */ + FaultToleranceConfig ANNOTATED = new FaultToleranceConfig() { + // uses default methods + }; + + /* + * General + */ + + default boolean isEnabled(InvocationContext context) { + return true; + } + + default boolean isEnabled(Class annotationType, InvocationContext context) { + return true; + } + + default boolean isMetricsEnabled(InvocationContext context) { + return true; + } + + default A getAnnotation(Class annotationType, InvocationContext context) { + A annotation = context.getMethod().getAnnotation(annotationType); + return annotation != null ? annotation : context.getMethod().getDeclaringClass().getAnnotation(annotationType); + } + + default boolean isAnnotationPresent(Class annotationType, InvocationContext context) { + return getAnnotation(annotationType, context) != null; + } + + + /* + * Retry + */ + + default int maxRetries(Retry annotation, InvocationContext context) { + return annotation.maxRetries(); + } + + default long delay(Retry annotation, InvocationContext context) { + return annotation.delay(); + } + + default ChronoUnit delayUnit(Retry annotation, InvocationContext context) { + return annotation.delayUnit(); + } + + default long maxDuration(Retry annotation, InvocationContext context) { + return annotation.maxDuration(); + } + + default ChronoUnit durationUnit(Retry annotation, InvocationContext context) { + return annotation.durationUnit(); + } + + default long jitter(Retry annotation, InvocationContext context) { + return annotation.jitter(); + } + + default ChronoUnit jitterDelayUnit(Retry annotation, InvocationContext context) { + return annotation.jitterDelayUnit(); + } + + default Class[] retryOn(Retry annotation, InvocationContext context) { + return annotation.retryOn(); + } + + default Class[] abortOn(Retry annotation, InvocationContext context) { + return annotation.abortOn(); + } + + + /* + * Circuit-Breaker + */ + + default Class[] failOn(CircuitBreaker annotation, InvocationContext context) { + return annotation.failOn(); + } + + default long delay(CircuitBreaker annotation, InvocationContext context) { + return annotation.delay(); + } + + default ChronoUnit delayUnit(CircuitBreaker annotation, InvocationContext context) { + return annotation.delayUnit(); + } + + default int requestVolumeThreshold(CircuitBreaker annotation, InvocationContext context) { + return annotation.requestVolumeThreshold(); + } + + default double failureRatio(CircuitBreaker annotation, InvocationContext context) { + return annotation.failureRatio(); + } + + default int successThreshold(CircuitBreaker annotation, InvocationContext context) { + return annotation.successThreshold(); + } + + + /* + * Bulkhead + */ + + default int value(Bulkhead annotation, InvocationContext context) { + return annotation.value(); + } + + default int waitingTaskQueue(Bulkhead annotation, InvocationContext context) { + return annotation.waitingTaskQueue(); + } + + + /* + * Timeout + */ + + default long value(Timeout annotation, InvocationContext context) { + return annotation.value(); + } + + default ChronoUnit unit(Timeout annotation, InvocationContext context) { + return annotation.unit(); + } + + + /* + * Fallback + */ + + default Class> value(Fallback annotation, InvocationContext context) { + return annotation.value(); + } + + default String fallbackMethod(Fallback annotation, InvocationContext context) { + return annotation.fallbackMethod(); + } +} diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/FaultToleranceExecution.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/FaultToleranceExecution.java new file mode 100644 index 00000000000..ef44f9b3f7e --- /dev/null +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/FaultToleranceExecution.java @@ -0,0 +1,32 @@ +package fish.payara.microprofile.faulttolerance; + +import java.util.concurrent.Future; + +import javax.interceptor.InvocationContext; + +import fish.payara.microprofile.faulttolerance.state.BulkheadSemaphore; +import fish.payara.microprofile.faulttolerance.state.CircuitBreakerState; + +public interface FaultToleranceExecution { + + CircuitBreakerState getState(int requestVolumeThreshold, InvocationContext context); + + void scheduleHalfOpen(long delayMillis, CircuitBreakerState circuitBreakerState) throws Exception; + + BulkheadSemaphore getExecutionSemaphoreOf(int maxConcurrentThreads, InvocationContext context); + + BulkheadSemaphore getWaitingQueueSemaphoreOf(int queueCapacity, InvocationContext context); + + Future runAsynchronous(InvocationContext context) throws Exception; + + //completeAsynchronous + + /** + * @return A future that can be cancelled if the method execution completes before the interrupt happens + */ + Future timeoutIn(long timeoutMillis) throws Exception; + + void startTrace(String method, InvocationContext context); + + void endTrace(); +} diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/FaultToleranceMetrics.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/FaultToleranceMetrics.java new file mode 100644 index 00000000000..161ef094c3a --- /dev/null +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/FaultToleranceMetrics.java @@ -0,0 +1,176 @@ +package fish.payara.microprofile.faulttolerance; + +import java.lang.annotation.Annotation; +import java.util.function.LongSupplier; + +import javax.interceptor.InvocationContext; + +import org.eclipse.microprofile.faulttolerance.Bulkhead; +import org.eclipse.microprofile.faulttolerance.CircuitBreaker; +import org.eclipse.microprofile.faulttolerance.Fallback; +import org.eclipse.microprofile.faulttolerance.Retry; +import org.eclipse.microprofile.faulttolerance.Timeout; + +/** + * Encodes the specifics of the FT metrics names using default methods while decoupling + * {@link org.eclipse.microprofile.metrics.MetricRegistry}. + * + * @author Jan Bernitt + */ +public interface FaultToleranceMetrics { + + /* + * Generic (to be implemented) + */ + + /** + * Counters: + * + * @param metric + * @param annotationType + * @param context + */ + void increment(String metric, Class annotationType, InvocationContext context); + + /** + * Histogram: + * + * @param metric + * @param nanos + * @param annotationType + * @param context + */ + void add(String metric, long nanos, Class annotationType, InvocationContext context); + + /** + * Gauge: + * + * @param metric + * @param gauge + * @param annotationType + * @param context + */ + void insert(String metric, LongSupplier gauge, Class annotationType, InvocationContext context); + + + /* + * @Retry, @Timeout, @CircuitBreaker, @Bulkhead and @Fallback + */ + + default void incrementInvocationsTotal(Class annotationType, InvocationContext context) { + increment("ft.%s.invocations.total", annotationType, context); + } + + default void incrementInvocationsFailedTotal(Class annotationType, InvocationContext context) { + increment("ft.%s.invocations.failed.total", annotationType, context); + } + + + /* + * @Retry + */ + + default void incrementRetryCallsSucceededNotRetriedTotal(InvocationContext context) { + increment("ft.%s.retry.callsSucceededNotRetried.total", Retry.class, context); + } + + default void incrementRetryCallsSucceededRetriedTotal(InvocationContext context) { + increment("ft.%s.retry.callsSucceededRetried.total", Retry.class, context); + } + + default void incrementRetryCallsFailedTotal(InvocationContext context) { + increment("ft.%s.retry.callsFailed.total", Retry.class, context); + } + + default void incrementRetryRetriesTotal(InvocationContext context) { + increment("ft.%s.retry.retries.total", Retry.class, context); + } + + + /* + * @Timeout + */ + + default void addTimeoutExecutionDuration(long duration, InvocationContext context) { + add("ft.%s.timeout.executionDuration", duration, Timeout.class, context); + } + + default void incrementTimeoutCallsTimedOutTotal(InvocationContext context) { + increment("ft.%s.timeout.callsTimedOut.total", Timeout.class, context); + } + + default void incrementTimeoutCallsNotTimedOutTotal(InvocationContext context) { + increment("ft.%s.timeout.callsNotTimedOut.total", Timeout.class, context); + } + + + /* + * @CircuitBreaker + */ + + default void incrementCircuitbreakerCallsSucceededTotal(InvocationContext context) { + increment("ft.%s.circuitbreaker.callsSucceeded.total", CircuitBreaker.class, context); + } + + default void incrementCircuitbreakerCallsFailedTotal(InvocationContext context) { + increment("ft.%s.circuitbreaker.callsFailed.total", CircuitBreaker.class, context); + } + + default void incrementCircuitbreakerCallsPreventedTotal(InvocationContext context) { + increment("ft.%s.circuitbreaker.callsPrevented.total", CircuitBreaker.class, context); + } + + default void incrementCircuitbreakerOpenedTotal(InvocationContext context) { + increment("ft.%s.circuitbreaker.opened.total", CircuitBreaker.class, context); + } + + default void insertCircuitbreakerOpenTotal(LongSupplier gauge, InvocationContext context) { + insert("ft.%s.circuitbreaker.open.total", gauge, CircuitBreaker.class, context); + } + + default void insertCircuitbreakerHalfOpenTotal(LongSupplier gauge, InvocationContext context) { + insert("ft.%s.circuitbreaker.halfOpen.total", gauge, CircuitBreaker.class, context); + } + + default void insertCircuitbreakerClosedTotal(LongSupplier gauge, InvocationContext context) { + insert("ft.%s.circuitbreaker.closed.total", gauge, CircuitBreaker.class, context); + } + + + /* + * @Bulkhead + */ + + default void incrementBulkheadCallsAcceptedTotal(InvocationContext context) { + increment("ft.%s.bulkhead.callsAccepted.total", Bulkhead.class, context); + } + + default void incrementBulkheadCallsRejectedTotal(InvocationContext context) { + increment("ft.%s.bulkhead.callsRejected.total", Bulkhead.class, context); + } + + default void insertBulkheadConcurrentExecutions(LongSupplier gauge, InvocationContext context) { + insert("ft.%s.bulkhead.concurrentExecutions", gauge, Bulkhead.class, context); + } + + default void insertBulkheadWaitingQueuePopulation(LongSupplier gauge, InvocationContext context) { + insert("ft.%s.bulkhead.waitingQueue.population", gauge, Bulkhead.class, context); + } + + default void addBulkheadExecutionDuration(long duration, InvocationContext context) { + add("ft.%s.bulkhead.executionDuration", duration, Bulkhead.class, context); + } + + default void addBulkheadWaitingDuration(long duration, InvocationContext context) { + add("ft.%s.bulkhead.waiting.duration", duration, Bulkhead.class, context); + } + + + /* + * @Fallback + */ + + default void incrementFallbackCallsTotal(InvocationContext context) { + increment("ft.%s.fallback.calls.total", Fallback.class, context); + } +} diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/FaultToleranceService.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/FaultToleranceService.java index bfebb1e62b4..51963ceb89f 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/FaultToleranceService.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/FaultToleranceService.java @@ -39,14 +39,17 @@ */ package fish.payara.microprofile.faulttolerance; +import fish.payara.microprofile.faulttolerance.state.BulkheadSemaphore; import fish.payara.microprofile.faulttolerance.state.CircuitBreakerState; import fish.payara.notification.requesttracing.RequestTraceSpan; import fish.payara.nucleus.requesttracing.RequestTracingService; + import java.lang.reflect.Method; import java.util.Arrays; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.Semaphore; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; import java.util.logging.Level; import java.util.logging.Logger; import javax.annotation.PostConstruct; @@ -57,19 +60,18 @@ import javax.interceptor.InvocationContext; import javax.naming.InitialContext; import javax.naming.NamingException; -import org.eclipse.microprofile.config.Config; -import org.eclipse.microprofile.faulttolerance.CircuitBreaker; -import org.eclipse.microprofile.metrics.MetricRegistry; import org.glassfish.api.StartupRunLevel; import org.glassfish.api.admin.ServerEnvironment; import org.glassfish.api.event.EventListener; import org.glassfish.api.event.Events; +import org.glassfish.api.invocation.ComponentInvocation; import org.glassfish.api.invocation.InvocationManager; import org.glassfish.hk2.api.ServiceLocator; import org.glassfish.hk2.runlevel.RunLevel; import org.glassfish.internal.data.ApplicationInfo; import org.glassfish.internal.data.ApplicationRegistry; import org.glassfish.internal.deployment.Deployment; +import org.jvnet.hk2.annotations.ContractsProvided; import org.jvnet.hk2.annotations.Optional; import org.jvnet.hk2.annotations.Service; @@ -78,43 +80,39 @@ * * @author Andrew Pielage */ +@ContractsProvided(FaultToleranceExecution.class) @Service(name = "microprofile-fault-tolerance-service") @RunLevel(StartupRunLevel.VAL) -public class FaultToleranceService implements EventListener { - - public static final String FAULT_TOLERANCE_ENABLED_PROPERTY = "MP_Fault_Tolerance_NonFallback_Enabled"; - public static final String METRICS_ENABLED_PROPERTY = "MP_Fault_Tolerance_Metrics_Enabled"; - public static final String FALLBACK_HANDLER_METHOD_NAME = "handle"; - +public class FaultToleranceService implements EventListener, FaultToleranceExecution { + private static final Logger logger = Logger.getLogger(FaultToleranceService.class.getName()); - + @Inject @Named(ServerEnvironment.DEFAULT_INSTANCE_NAME) @Optional - FaultToleranceServiceConfiguration faultToleranceServiceConfiguration; - + private FaultToleranceServiceConfiguration serviceConfig; + + private InvocationManager invocationManager; + @Inject - RequestTracingService requestTracingService; - + private RequestTracingService requestTracingService; + @Inject - ServiceLocator habitat; - + private ServiceLocator serviceLocator; + @Inject - Events events; - - private final Map faultToleranceObjects; - - public FaultToleranceService() { - faultToleranceObjects = new ConcurrentHashMap<>(); - } - + private Events events; + + private final Map stateByApplication = new ConcurrentHashMap<>(); + @PostConstruct public void postConstruct() { events.register(this); - faultToleranceServiceConfiguration = habitat.getService(FaultToleranceServiceConfiguration.class); - requestTracingService = habitat.getService(RequestTracingService.class); + serviceConfig = serviceLocator.getService(FaultToleranceServiceConfiguration.class); + invocationManager = serviceLocator.getService(InvocationManager.class); + requestTracingService = serviceLocator.getService(RequestTracingService.class); } - + @Override public void event(Event event) { if (event.is(Deployment.APPLICATION_UNLOADED)) { @@ -122,80 +120,30 @@ public void event(Event event) { deregisterApplication(info.getName()); } } - - /** - * Checks whether fault tolerance is enabled for a given application, setting its enabled value if it doesn't - * already have one. - * @param applicationName The application to check if Fault Tolerance is enabled for. - * @param config The application config to check for any override values when setting the enabled status for an - * unregistered application. - * @return True if Fault Tolerance is enabled for the given application name - */ - public Boolean isFaultToleranceEnabled(String applicationName, Config config) { - try { - if (faultToleranceObjects.containsKey(applicationName)) { - return faultToleranceObjects.get(applicationName).isEnabled(); - } - initialiseFaultToleranceObject(applicationName, config); - return faultToleranceObjects.get(applicationName).isEnabled(); - } catch (NullPointerException npe) { - initialiseFaultToleranceObject(applicationName, config); - return faultToleranceObjects.get(applicationName).isEnabled(); - } - } - - /** - * Checks whether fault tolerance metrics are enabled for a given application, setting its enabled value if it doesn't - * already have one. - * @param applicationName The application to check if Fault Tolerance metrics are enabled for. - * @param config The application config to check for any override values when setting the enabled status for an - * unregistered application. - * @return True if Fault Tolerance metrics are enabled for the given application name - */ - public Boolean areFaultToleranceMetricsEnabled(String applicationName, Config config) { - if (faultToleranceObjects.containsKey(applicationName)) { - return faultToleranceObjects.get(applicationName).areMetricsEnabled(); - } - initialiseFaultToleranceObject(applicationName, config); - return faultToleranceObjects.get(applicationName).areMetricsEnabled(); - } - + /** * Helper method that sets the enabled status for a given application. * @param applicationName The name of the application to register - * @param config The config to check for override values from + * @param serviceConfig The config to check for override values from */ - private synchronized void initialiseFaultToleranceObject(String applicationName, Config config) { + private void initialiseFaultToleranceObject(String applicationName) { // Double lock as multiple methods can get inside the calling if at the same time logger.log(Level.FINER, "Checking double lock to see if something else has added the application"); - if (!faultToleranceObjects.containsKey(applicationName)) { - if (config != null) { - // Set the enabled value to the override value from the config, or true if it isn't configured - faultToleranceObjects.put(applicationName, new FaultToleranceObject( - config.getOptionalValue(FAULT_TOLERANCE_ENABLED_PROPERTY, Boolean.class) - .orElse(Boolean.TRUE), - config.getOptionalValue(METRICS_ENABLED_PROPERTY, Boolean.class) - .orElse(Boolean.TRUE))); - } else { - logger.log(Level.FINE, "No config found, so enabling fault tolerance for application: {0}", - applicationName); - faultToleranceObjects.put(applicationName, new FaultToleranceObject(Boolean.TRUE, Boolean.TRUE)); - } - } + stateByApplication.computeIfAbsent(applicationName, key -> new FaultToleranceApplicationState()); } - + /** * Gets the configured ManagedExecutorService. * @return The configured ManagedExecutorService, or the default ManagedExecutorService if the configured one * couldn't be found * @throws NamingException If the default ManagedExecutorService couldn't be found */ - public ManagedExecutorService getManagedExecutorService() throws NamingException { - String managedExecutorServiceName = faultToleranceServiceConfiguration.getManagedExecutorService(); + private ManagedExecutorService getManagedExecutorService() throws NamingException { + String managedExecutorServiceName = serviceConfig.getManagedExecutorService(); InitialContext ctx = new InitialContext(); - + ManagedExecutorService managedExecutorService; - + // If no name has been set, just get the default if (managedExecutorServiceName == null || managedExecutorServiceName.isEmpty()) { managedExecutorService = (ManagedExecutorService) ctx.lookup("java:comp/DefaultManagedExecutorService"); @@ -208,23 +156,25 @@ public ManagedExecutorService getManagedExecutorService() throws NamingException managedExecutorService = (ManagedExecutorService) ctx.lookup("java:comp/DefaultManagedExecutorService"); } } - + return managedExecutorService; } + //TODO use the scheduler to schedule a clean of FT Info + /** * Gets the configured ManagedScheduledExecutorService. * @return The configured ManagedExecutorService, or the default ManagedScheduledExecutorService if the configured * one couldn't be found * @throws NamingException If the default ManagedScheduledExecutorService couldn't be found */ - public ManagedScheduledExecutorService getManagedScheduledExecutorService() throws NamingException { - String managedScheduledExecutorServiceName = faultToleranceServiceConfiguration + private ManagedScheduledExecutorService getManagedScheduledExecutorService() throws NamingException { + String managedScheduledExecutorServiceName = serviceConfig .getManagedScheduledExecutorService(); InitialContext ctx = new InitialContext(); - + ManagedScheduledExecutorService managedScheduledExecutorService = null; - + // If no name has been set, just get the default if (managedScheduledExecutorServiceName == null || managedScheduledExecutorServiceName.isEmpty()) { managedScheduledExecutorService = (ManagedScheduledExecutorService) ctx.lookup( @@ -240,10 +190,10 @@ public ManagedScheduledExecutorService getManagedScheduledExecutorService() thro "java:comp/DefaultManagedScheduledExecutorService"); } } - + return managedScheduledExecutorService; } - + /** * Gets the Bulkhead Execution Semaphore for a given application method, registering it to the * FaultToleranceService if it hasn't already. @@ -253,20 +203,20 @@ public ManagedScheduledExecutorService getManagedScheduledExecutorService() thro * @param bulkheadValue The value parameter of the Bulkhead annotation * @return The Semaphore for the given application method. */ - public Semaphore getBulkheadExecutionSemaphore(String applicationName, Object invocationTarget, + public BulkheadSemaphore getBulkheadExecutionSemaphore(String applicationName, Object invocationTarget, Method annotatedMethod, int bulkheadValue) { - Semaphore bulkheadExecutionSemaphore; + BulkheadSemaphore bulkheadExecutionSemaphore; String fullMethodSignature = getFullMethodSignature(annotatedMethod); - - Map annotatedMethodSemaphores = null; - + + Map annotatedMethodSemaphores = null; + try { - annotatedMethodSemaphores = faultToleranceObjects.get(applicationName).getBulkheadExecutionSemaphores() + annotatedMethodSemaphores = stateByApplication.get(applicationName).getBulkheadExecutionSemaphores() .get(invocationTarget); } catch (NullPointerException npe) { logger.log(Level.FINE, "NPE caught trying to get semaphores for annotated method", npe); } - + // If there isn't a semaphore registered for this bean, register one, otherwise just return // the one already registered if (annotatedMethodSemaphores == null) { @@ -275,7 +225,7 @@ public Semaphore getBulkheadExecutionSemaphore(String applicationName, Object in fullMethodSignature, bulkheadValue); } else { bulkheadExecutionSemaphore = annotatedMethodSemaphores.get(fullMethodSignature); - + // If there isn't a semaphore registered for this method signature, register one, otherwise just return // the one already registered if (bulkheadExecutionSemaphore == null) { @@ -285,10 +235,10 @@ public Semaphore getBulkheadExecutionSemaphore(String applicationName, Object in fullMethodSignature, bulkheadValue); } } - + return bulkheadExecutionSemaphore; } - + /** * Helper method to create and register a Bulkhead Execution Semaphore for an annotated method * @param applicationName The name of the application @@ -297,35 +247,35 @@ public Semaphore getBulkheadExecutionSemaphore(String applicationName, Object in * @param bulkheadValue The size of the bulkhead * @return The Bulkhead Execution Semaphore for the given method signature and application */ - private synchronized Semaphore createBulkheadExecutionSemaphore(String applicationName, Object invocationTarget, + private synchronized BulkheadSemaphore createBulkheadExecutionSemaphore(String applicationName, Object invocationTarget, String fullMethodSignature, int bulkheadValue) { - + // Double lock as multiple methods can get inside the calling if at the same time logger.log(Level.FINER, "Checking double lock to see if something else has already added the application to " + "the bulkhead execution semaphore map"); - if (faultToleranceObjects.get(applicationName).getBulkheadExecutionSemaphores().get(invocationTarget) == null) { + if (stateByApplication.get(applicationName).getBulkheadExecutionSemaphores().get(invocationTarget) == null) { logger.log(Level.FINER, "Registering bean to bulkhead execution semaphore map: {0}", invocationTarget); - - faultToleranceObjects.get(applicationName).getBulkheadExecutionSemaphores().put( + + stateByApplication.get(applicationName).getBulkheadExecutionSemaphores().put( invocationTarget, new ConcurrentHashMap<>()); } - + // Double lock as multiple methods can get inside the calling if at the same time logger.log(Level.FINER, "Checking double lock to see if something else has already added the annotated method " + "to the bulkhead execution semaphore map"); - if (faultToleranceObjects.get(applicationName).getBulkheadExecutionSemaphores().get(invocationTarget) + if (stateByApplication.get(applicationName).getBulkheadExecutionSemaphores().get(invocationTarget) .get(fullMethodSignature) == null) { logger.log(Level.FINER, "Registering semaphore for method {0} to the bulkhead execution semaphore map", fullMethodSignature); - faultToleranceObjects.get(applicationName).getBulkheadExecutionSemaphores().get(invocationTarget) - .put(fullMethodSignature, new Semaphore(bulkheadValue, true)); + stateByApplication.get(applicationName).getBulkheadExecutionSemaphores().get(invocationTarget) + .put(fullMethodSignature, new BulkheadSemaphore(bulkheadValue)); } - return faultToleranceObjects.get(applicationName).getBulkheadExecutionSemaphores().get(invocationTarget) + return stateByApplication.get(applicationName).getBulkheadExecutionSemaphores().get(invocationTarget) .get(fullMethodSignature); } - + /** * Gets the Bulkhead Execution Queue Semaphore for a given application method, registering it to the * FaultToleranceService if it hasn't already. @@ -335,14 +285,14 @@ private synchronized Semaphore createBulkheadExecutionSemaphore(String applicati * @param bulkheadWaitingTaskQueue The waitingTaskQueue parameter of the Bulkhead annotation * @return The Semaphore for the given application method. */ - public Semaphore getBulkheadExecutionQueueSemaphore(String applicationName, Object invocationTarget, + public BulkheadSemaphore getBulkheadExecutionQueueSemaphore(String applicationName, Object invocationTarget, Method annotatedMethod, int bulkheadWaitingTaskQueue) { - Semaphore bulkheadExecutionQueueSemaphore; + BulkheadSemaphore bulkheadExecutionQueueSemaphore; String fullMethodSignature = getFullMethodSignature(annotatedMethod); - - Map annotatedMethodExecutionQueueSemaphores = - faultToleranceObjects.get(applicationName).getBulkheadExecutionQueueSemaphores().get(invocationTarget); - + + Map annotatedMethodExecutionQueueSemaphores = + stateByApplication.get(applicationName).getBulkheadExecutionQueueSemaphores().get(invocationTarget); + // If there isn't a semaphore registered for this application name, register one, otherwise just return // the one already registered if (annotatedMethodExecutionQueueSemaphores == null) { @@ -351,7 +301,7 @@ public Semaphore getBulkheadExecutionQueueSemaphore(String applicationName, Obje fullMethodSignature, bulkheadWaitingTaskQueue); } else { bulkheadExecutionQueueSemaphore = annotatedMethodExecutionQueueSemaphores.get(fullMethodSignature); - + // If there isn't a semaphore registered for this method signature, register one, otherwise just return // the one already registered if (bulkheadExecutionQueueSemaphore == null) { @@ -361,10 +311,10 @@ public Semaphore getBulkheadExecutionQueueSemaphore(String applicationName, Obje fullMethodSignature, bulkheadWaitingTaskQueue); } } - + return bulkheadExecutionQueueSemaphore; } - + /** * Helper method to create and register a Bulkhead Execution Queue Semaphore for an annotated method * @param applicationName The name of the application @@ -373,34 +323,32 @@ public Semaphore getBulkheadExecutionQueueSemaphore(String applicationName, Obje * @param bulkheadWaitingTaskQueue The size of the waiting task queue of the bulkhead * @return The Bulkhead Execution Queue Semaphore for the given method signature and application */ - private synchronized Semaphore createBulkheadExecutionQueueSemaphore(String applicationName, Object invocationTarget, + private synchronized BulkheadSemaphore createBulkheadExecutionQueueSemaphore(String applicationName, Object invocationTarget, String fullMethodSignature, int bulkheadWaitingTaskQueue) { // Double lock as multiple methods can get inside the calling if at the same time logger.log(Level.FINER, "Checking double lock to see if something else has already added the object to " + "the bulkhead execution queue semaphore map"); - if (faultToleranceObjects.get(applicationName).getBulkheadExecutionQueueSemaphores().get(invocationTarget) - == null) { + FaultToleranceApplicationState applicationState = stateByApplication.get(applicationName); + Map> applicationSemaphores = applicationState.getBulkheadExecutionQueueSemaphores(); + if (applicationSemaphores.get(invocationTarget) == null) { logger.log(Level.FINER, "Registering object to the bulkhead execution queue semaphore map: {0}", invocationTarget); - faultToleranceObjects.get(applicationName).getBulkheadExecutionQueueSemaphores() - .put(invocationTarget, new ConcurrentHashMap<>()); + applicationSemaphores.put(invocationTarget, new ConcurrentHashMap<>()); } - + // Double lock as multiple methods can get inside the calling if at the same time logger.log(Level.FINER, "Checking double lock to see if something else has already added the annotated method " + "to the bulkhead execution queue semaphore map"); - if (faultToleranceObjects.get(applicationName).getBulkheadExecutionQueueSemaphores().get(invocationTarget). - get(fullMethodSignature) == null) { + if (applicationSemaphores.get(invocationTarget).get(fullMethodSignature) == null) { logger.log(Level.FINER, "Registering semaphore for method {0} to the bulkhead execution semaphore map", fullMethodSignature); - faultToleranceObjects.get(applicationName).getBulkheadExecutionQueueSemaphores().get(invocationTarget) - .put(fullMethodSignature, new Semaphore(bulkheadWaitingTaskQueue, true)); + applicationSemaphores.get(invocationTarget).put(fullMethodSignature, + new BulkheadSemaphore(bulkheadWaitingTaskQueue)); } - return faultToleranceObjects.get(applicationName).getBulkheadExecutionQueueSemaphores().get(invocationTarget) - .get(fullMethodSignature); + return applicationSemaphores.get(invocationTarget).get(fullMethodSignature); } - + /** * Gets the CircuitBreakerState object for a given application name and method.If a CircuitBreakerState hasn't been * registered for the given application name and method, it will register the given CircuitBreaker. @@ -410,35 +358,35 @@ private synchronized Semaphore createBulkheadExecutionQueueSemaphore(String appl * @param circuitBreaker The @CircuitBreaker annotation from the annotated method * @return The CircuitBreakerState for the given application and method */ - public CircuitBreakerState getCircuitBreakerState(String applicationName, Object invocationTarget, - Method annotatedMethod, CircuitBreaker circuitBreaker) { + private CircuitBreakerState getCircuitBreakerState(String applicationName, Object invocationTarget, + Method annotatedMethod, int requestVolumeThreshold) { CircuitBreakerState circuitBreakerState; String fullMethodSignature = getFullMethodSignature(annotatedMethod); - + Map annotatedMethodCircuitBreakerStates = - faultToleranceObjects.get(applicationName).getCircuitBreakerStates().get(invocationTarget); + stateByApplication.get(applicationName).getCircuitBreakerStates().get(invocationTarget); // If there isn't a CircuitBreakerState registered for this application name, register one, otherwise just // return the one already registered if (annotatedMethodCircuitBreakerStates == null) { logger.log(Level.FINER, "No matching object in the circuit breaker states map, registering..."); circuitBreakerState = registerCircuitBreaker(applicationName, invocationTarget, fullMethodSignature, - circuitBreaker); + requestVolumeThreshold); } else { circuitBreakerState = annotatedMethodCircuitBreakerStates.get(fullMethodSignature); - + // If there isn't a CircuitBreakerState registered for this method, register one, otherwise just // return the one already registered if (circuitBreakerState == null) { logger.log(Level.FINER, "No matching method in the circuit breaker states map, registering..."); circuitBreakerState = registerCircuitBreaker(applicationName, invocationTarget, fullMethodSignature, - circuitBreaker); + requestVolumeThreshold); } } - + return circuitBreakerState; } - + /** * Helper method to create and register a CircuitBreakerState object for an annotated method * @param applicationName The application name to register the CircuitBreakerState against @@ -447,29 +395,29 @@ public CircuitBreakerState getCircuitBreakerState(String applicationName, Object * @return The CircuitBreakerState object for the given method signature and application */ private synchronized CircuitBreakerState registerCircuitBreaker(String applicationName, Object invocationTarget, - String fullMethodSignature, CircuitBreaker circuitBreaker) { + String fullMethodSignature, int requestVolumeThreshold) { // Double lock as multiple methods can get inside the calling if at the same time logger.log(Level.FINER, "Checking double lock to see if something else has already added the object " + "to the circuit breaker states map"); - if (faultToleranceObjects.get(applicationName).getCircuitBreakerStates().get(invocationTarget) == null) { + Map> applicationStates = stateByApplication.get(applicationName).getCircuitBreakerStates(); + Map targetStates = applicationStates.get(invocationTarget); + if (targetStates == null) { logger.log(Level.FINER, "Registering application to the circuit breaker states map: {0}", invocationTarget); - faultToleranceObjects.get(applicationName).getCircuitBreakerStates().put(invocationTarget, - new ConcurrentHashMap<>()); + applicationStates.put(invocationTarget, new ConcurrentHashMap<>()); } - + // Double lock as multiple methods can get inside the calling if at the same time logger.log(Level.FINER, "Checking double lock to see if something else has already added the annotated method " + "to the circuit breaker states map"); - if (faultToleranceObjects.get(applicationName).getCircuitBreakerStates().get(invocationTarget) - .get(fullMethodSignature) == null) { + if (targetStates.get(fullMethodSignature) == null) { logger.log(Level.FINER, "Registering CircuitBreakerState for method {0} to the circuit breaker states map", fullMethodSignature); - faultToleranceObjects.get(applicationName).getCircuitBreakerStates().get(invocationTarget) - .put(fullMethodSignature, new CircuitBreakerState(circuitBreaker.requestVolumeThreshold())); + targetStates + .put(fullMethodSignature, new CircuitBreakerState(requestVolumeThreshold)); } - return faultToleranceObjects.get(applicationName).getCircuitBreakerStates().get(invocationTarget).get(fullMethodSignature); + return targetStates.get(fullMethodSignature); } /** @@ -477,47 +425,39 @@ private synchronized CircuitBreakerState registerCircuitBreaker(String applicati * @param applicationName The name of the application to remove */ private void deregisterApplication(String applicationName) { - faultToleranceObjects.remove(applicationName); + stateByApplication.remove(applicationName); } - + /** * Gets the application name from the invocation manager. Failing that, it will use the module name, component name, * or method signature (in that order). * @param invocationManager The invocation manager to get the application name from - * @param invocationContext The context of the current invocation + * @param context The context of the current invocation * @return The application name */ - public String getApplicationName(InvocationManager invocationManager, InvocationContext invocationContext) { - String appName = invocationManager.getCurrentInvocation().getAppName(); - if (appName == null) { - appName = invocationManager.getCurrentInvocation().getModuleName(); - - if (appName == null) { - appName = invocationManager.getCurrentInvocation().getComponentId(); - - // If we've found a component name, check if there's an application registered with the same name - if (appName != null) { - ApplicationRegistry applicationRegistry = habitat.getService(ApplicationRegistry.class); - - // If it's not directly in the registry, it's possible due to how the componentId is constructed - if (applicationRegistry.get(appName) == null) { - String[] componentIds = appName.split("_/"); - - // The application name should be the first component - appName = componentIds[0]; - } - } - - // If we still don't have a name - just construct it from the method signature - if (appName == null) { - appName = getFullMethodSignature(invocationContext.getMethod()); - } + private String getApplicationName(InvocationContext context) { + ComponentInvocation currentInvocation = invocationManager.getCurrentInvocation(); + String appName = currentInvocation.getAppName(); + if (appName != null) { + return appName; + } + appName = currentInvocation.getModuleName(); + if (appName != null) { + return appName; + } + appName = currentInvocation.getComponentId(); + // If we've found a component name, check if there's an application registered with the same name + if (appName != null) { + // If it's not directly in the registry, it's possible due to how the componentId is constructed + if (serviceLocator.getService(ApplicationRegistry.class).get(appName) == null) { + // The application name should be the first component + return appName.split("_/")[0]; } } - - return appName; + // If we still don't have a name - just construct it from the method signature + return getFullMethodSignature(context.getMethod()); } - + /** * Helper method to generate a full method signature consisting of canonical class name, method name, * parameter types, and return type. @@ -530,48 +470,86 @@ private static String getFullMethodSignature(Method annotatedMethod) { + "(" + Arrays.toString(annotatedMethod.getParameterTypes()) + ")" + ">" + annotatedMethod.getReturnType().getSimpleName(); } - - public void startFaultToleranceSpan(RequestTraceSpan span, InvocationManager invocationManager, - InvocationContext invocationContext) { + + private void startFaultToleranceSpan(RequestTraceSpan span, InvocationContext invocationContext) { if (requestTracingService != null && requestTracingService.isRequestTracingEnabled()) { - addGenericFaultToleranceRequestTracingDetails(span, invocationManager, invocationContext); + addGenericFaultToleranceRequestTracingDetails(span, invocationContext); requestTracingService.startTrace(span); } } - - public void endFaultToleranceSpan() { + + private void endFaultToleranceSpan() { if (requestTracingService != null && requestTracingService.isRequestTracingEnabled()) { requestTracingService.endTrace(); } } - - private static void addGenericFaultToleranceRequestTracingDetails(RequestTraceSpan span, - InvocationManager invocationManager, InvocationContext invocationContext) { + + private void addGenericFaultToleranceRequestTracingDetails(RequestTraceSpan span, + InvocationContext invocationContext) { span.addSpanTag("App Name", invocationManager.getCurrentInvocation().getAppName()); span.addSpanTag("Component ID", invocationManager.getCurrentInvocation().getComponentId()); span.addSpanTag("Module Name", invocationManager.getCurrentInvocation().getModuleName()); span.addSpanTag("Class Name", invocationContext.getMethod().getDeclaringClass().getName()); span.addSpanTag("Method Name", invocationContext.getMethod().getName()); } - - public void incrementCounterMetric(MetricRegistry metricRegistry, String metricName, String applicationName, - Config config) { - if (areFaultToleranceMetricsEnabled(applicationName, config)) { - metricRegistry.counter(metricName).inc(); - } + + + /* + * Execution + */ + + @Override + public CircuitBreakerState getState(int requestVolumeThreshold, InvocationContext context) { + return getCircuitBreakerState(getApplicationName(context), context.getTarget(), + context.getMethod(), requestVolumeThreshold); } - - public void updateHistogramMetric(MetricRegistry metricRegistry, String metricName, int value, - String applicationName, Config config) { - if (areFaultToleranceMetricsEnabled(applicationName, config)) { - metricRegistry.histogram(metricName).update(value); - } + + /** + * Helper method that schedules the CircuitBreaker state to be set to HalfOpen after the configured delay + * @param delayMillis The number of milliseconds to wait before setting the state + * @param circuitBreakerState The CircuitBreakerState to set the state of + * @throws NamingException If the ManagedScheduledExecutor couldn't be found + */ + @Override + public void scheduleHalfOpen(long delayMillis, CircuitBreakerState circuitBreakerState) throws NamingException { + Runnable halfOpen = () -> { + circuitBreakerState.setCircuitState(CircuitBreakerState.CircuitState.HALF_OPEN); + logger.log(Level.FINE, "Setting CircuitBreaker state to half open"); + }; + getManagedScheduledExecutorService().schedule(halfOpen, delayMillis, TimeUnit.MILLISECONDS); + logger.log(Level.FINER, "CircuitBreaker half open state scheduled in {0} milliseconds", delayMillis); } - - public void updateHistogramMetric(MetricRegistry metricRegistry, String metricName, long value, - String applicationName, Config config) { - if (areFaultToleranceMetricsEnabled(applicationName, config)) { - metricRegistry.histogram(metricName).update(value); - } + + @Override + public BulkheadSemaphore getExecutionSemaphoreOf(int maxConcurrentThreads, InvocationContext context) { + return getBulkheadExecutionSemaphore(getApplicationName(context), + context.getTarget(), context.getMethod(), maxConcurrentThreads); + } + + @Override + public BulkheadSemaphore getWaitingQueueSemaphoreOf(int queueCapacity, InvocationContext context) { + return getBulkheadExecutionQueueSemaphore(getApplicationName(context), + context.getTarget(), context.getMethod(), queueCapacity); + } + + @Override + public Future runAsynchronous(InvocationContext context) throws Exception { + return getManagedExecutorService().submit(() -> context.proceed()); + } + + @Override + public Future timeoutIn(long millis) throws Exception { + final Thread thread = Thread.currentThread(); + return getManagedScheduledExecutorService().schedule(thread::interrupt, millis, TimeUnit.MILLISECONDS); + } + + @Override + public void startTrace(String method, InvocationContext context) { + startFaultToleranceSpan(new RequestTraceSpan(method), context); + } + + @Override + public void endTrace() { + endFaultToleranceSpan(); } } diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/cdi/CdiFaultToleranceConfig.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/cdi/CdiFaultToleranceConfig.java new file mode 100644 index 00000000000..1909cd39509 --- /dev/null +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/cdi/CdiFaultToleranceConfig.java @@ -0,0 +1,273 @@ +package fish.payara.microprofile.faulttolerance.cdi; + +import java.io.Serializable; +import java.lang.annotation.Annotation; +import java.time.temporal.ChronoUnit; +import java.util.ArrayList; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.Optional; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.interceptor.InvocationContext; + +import org.eclipse.microprofile.config.Config; +import org.eclipse.microprofile.config.ConfigProvider; +import org.eclipse.microprofile.faulttolerance.Bulkhead; +import org.eclipse.microprofile.faulttolerance.CircuitBreaker; +import org.eclipse.microprofile.faulttolerance.Fallback; +import org.eclipse.microprofile.faulttolerance.FallbackHandler; +import org.eclipse.microprofile.faulttolerance.Retry; +import org.eclipse.microprofile.faulttolerance.Timeout; + +import fish.payara.microprofile.faulttolerance.FaultToleranceConfig; +import fish.payara.microprofile.faulttolerance.cdi.FaultToleranceCdiUtils.Stereotypes; + +/** + * A {@link FaultToleranceConfig} using {@link Config} to resolve overrides. + * The {@link Config} is resolved using the {@link ConfigProvider} if needed. + * + * @author Jan Bernitt + */ +public class CdiFaultToleranceConfig implements FaultToleranceConfig, Serializable { + + public static final String FAULT_TOLERANCE_ENABLED_PROPERTY = "MP_Fault_Tolerance_NonFallback_Enabled"; + public static final String METRICS_ENABLED_PROPERTY = "MP_Fault_Tolerance_Metrics_Enabled"; + + private static final Logger logger = Logger.getLogger(CdiFaultToleranceConfig.class.getName()); + + private final Stereotypes sterotypes; + private transient Config config; + + public CdiFaultToleranceConfig(Config config, Stereotypes sterotypes) { + this.sterotypes = sterotypes; + this.config = config; + } + + private Config getConfig() { + if (config == null) { + logger.log(Level.INFO, "Resolving Fault Tolerance Config from Provider."); + try { + config = ConfigProvider.getConfig(); + } catch (IllegalArgumentException ex) { + logger.log(Level.INFO, "No config could be found", ex); + } + } + return config; + } + + + /* + * General + */ + + @Override + public boolean isEnabled(InvocationContext context) { + return getConfig().getOptionalValue(FAULT_TOLERANCE_ENABLED_PROPERTY, Boolean.class).orElse(true); + } + + @Override + public boolean isEnabled(Class annotationType, InvocationContext context) { + return FaultToleranceCdiUtils.getEnabledOverrideValue(getConfig(), annotationType, context).orElse(true); + } + + @Override + public boolean isMetricsEnabled(InvocationContext context) { + return getConfig().getOptionalValue(METRICS_ENABLED_PROPERTY, Boolean.class).orElse(true); + } + + @Override + public A getAnnotation(Class annotationType, InvocationContext context) { + return FaultToleranceCdiUtils.getAnnotation(sterotypes, annotationType, context); + } + + /* + * Retry + */ + + @Override + public int maxRetries(Retry annotation, InvocationContext context) { + return intValue(Retry.class, "maxRetries", context, annotation.maxRetries()); + } + + @Override + public long delay(Retry annotation, InvocationContext context) { + return longValue(Retry.class, "delay", context, annotation.delay()); + } + + @Override + public ChronoUnit delayUnit(Retry annotation, InvocationContext context) { + return chronoUnitValue(Retry.class, "delayUnit", context, annotation.delayUnit()); + } + + @Override + public long maxDuration(Retry annotation, InvocationContext context) { + return longValue(Retry.class, "maxDuration", context, annotation.maxDuration()); + } + + @Override + public ChronoUnit durationUnit(Retry annotation, InvocationContext context) { + return chronoUnitValue(Retry.class, "durationUnit", context, annotation.durationUnit()); + } + + @Override + public long jitter(Retry annotation, InvocationContext context) { + return longValue(Retry.class, "jitter", context, annotation.jitter()); + } + + @Override + public ChronoUnit jitterDelayUnit(Retry annotation, InvocationContext context) { + return chronoUnitValue(Retry.class, "jitterDelayUnit", context, annotation.jitterDelayUnit()); + } + + @Override + public Class[] retryOn(Retry annotation, InvocationContext context) { + return getClassArrayValue(Retry.class, "retryOn", context, annotation.retryOn()); + } + + @Override + public Class[] abortOn(Retry annotation, InvocationContext context) { + return getClassArrayValue(Retry.class, "abortOn", context, annotation.abortOn()); + } + + + /* + * Circuit-Breaker + */ + + @Override + public Class[] failOn(CircuitBreaker annotation, InvocationContext context) { + return getClassArrayValue(CircuitBreaker.class, "failOn", context, annotation.failOn()); + } + + @Override + public long delay(CircuitBreaker annotation, InvocationContext context) { + return longValue(CircuitBreaker.class, "delay", context, annotation.delay()); + } + + @Override + public ChronoUnit delayUnit(CircuitBreaker annotation, InvocationContext context) { + return chronoUnitValue(CircuitBreaker.class, "delayUnit", context, annotation.delayUnit()); + } + + @Override + public int requestVolumeThreshold(CircuitBreaker annotation, InvocationContext context) { + return intValue(CircuitBreaker.class, "requestVolumeThreshold", context, annotation.requestVolumeThreshold()); + } + + @Override + public double failureRatio(CircuitBreaker annotation, InvocationContext context) { + return value(CircuitBreaker.class, "failureRatio", context, Double.class, annotation.failureRatio()); + } + + @Override + public int successThreshold(CircuitBreaker annotation, InvocationContext context) { + return intValue(CircuitBreaker.class, "successThreshold", context, annotation.successThreshold()); + } + + + /* + * Bulkhead + */ + + @Override + public int value(Bulkhead annotation, InvocationContext context) { + return intValue(Bulkhead.class, "value", context, annotation.value()); + } + + @Override + public int waitingTaskQueue(Bulkhead annotation, InvocationContext context) { + return intValue(Bulkhead.class, "waitingTaskQueue", context, annotation.waitingTaskQueue()); + } + + + /* + * Timeout + */ + + @Override + public long value(Timeout annotation, InvocationContext context) { + return longValue(Timeout.class, "value", context, annotation.value()); + } + + @Override + public ChronoUnit unit(Timeout annotation, InvocationContext context) { + return chronoUnitValue(Timeout.class, "unit", context, annotation.unit()); + } + + + /* + * Fallback + */ + + @SuppressWarnings("unchecked") + @Override + public Class> value(Fallback annotation, InvocationContext context) { + Optional className = FaultToleranceCdiUtils.getOverrideValue(getConfig(), Fallback.class, "value", + context, String.class); + if (className.isPresent()) { + try { + return (Class>) Thread.currentThread().getContextClassLoader() + .loadClass(className.get()); + } catch (ClassNotFoundException e) { + // fall through + } + } + return annotation.value(); + } + + @Override + public String fallbackMethod(Fallback annotation, InvocationContext context) { + return value(Fallback.class, "fallbackMethod", context, String.class, annotation.fallbackMethod()); + } + + + /* + * Helpers + */ + + private long longValue(Class annotationType, String attribute, InvocationContext context, + long annotationValue) { + return value(annotationType, attribute, context, Long.class, annotationValue); + } + + private int intValue(Class annotationType, String attribute, InvocationContext context, + int annotationValue) { + return value(annotationType, attribute, context, Integer.class, annotationValue); + } + + private ChronoUnit chronoUnitValue(Class annotationType, String attribute, + InvocationContext context, ChronoUnit annotationValue) { + return value(annotationType, attribute, context, ChronoUnit.class, annotationValue); + } + + private T value(Class annotationType, String attribute, + InvocationContext context, Class valueType, T annotationValue) { + return FaultToleranceCdiUtils.getOverrideValue(getConfig(), annotationType, attribute, context, valueType) + .orElse(annotationValue); + } + + private Class[] getClassArrayValue(Class annotationType, + String attributeName, InvocationContext context, Class[] annotationValue) { + try { + Optional classNames = FaultToleranceCdiUtils.getOverrideValue( + getConfig(), annotationType, attributeName, context, String.class); + if (classNames.isPresent()) { + List> classList = new ArrayList<>(); + // Remove any curly or square brackets from the string, as well as any spaces and ".class"es + for (String className : classNames.get().replaceAll("[\\{\\[ \\]\\}]", "") + .replaceAll("\\.class", "").split(",")) { + classList.add(Class.forName(className)); + } + return classList.toArray(annotationValue); + } + } catch (NoSuchElementException nsee) { + logger.log(Level.FINER, "Could not find element in config", nsee); + } catch (ClassNotFoundException cnfe) { + logger.log(Level.INFO, "Could not find class from " + attributeName + " config, defaulting to annotation. " + + "Make sure you give the full canonical class name.", cnfe); + } + return annotationValue; + } +} diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/cdi/CdiFaultToleranceMetrics.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/cdi/CdiFaultToleranceMetrics.java new file mode 100644 index 00000000000..c070b092c39 --- /dev/null +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/cdi/CdiFaultToleranceMetrics.java @@ -0,0 +1,61 @@ +package fish.payara.microprofile.faulttolerance.cdi; + +import java.lang.annotation.Annotation; +import java.util.function.LongSupplier; + +import javax.enterprise.inject.spi.CDI; +import javax.interceptor.InvocationContext; + +import org.eclipse.microprofile.metrics.Gauge; +import org.eclipse.microprofile.metrics.MetricRegistry; + +import fish.payara.microprofile.faulttolerance.FaultToleranceMetrics; + +/** + * A {@link FaultToleranceMetrics} service that uses {@link CDI} to resolve the {@link MetricRegistry} if needed. + * + * @author Jan Bernitt + */ +public class CdiFaultToleranceMetrics implements FaultToleranceMetrics { + + private MetricRegistry metricRegistry; + + public CdiFaultToleranceMetrics(MetricRegistry metricRegistry) { + this.metricRegistry = metricRegistry; + } + + @Override + public void increment(String keyPattern, Class annotationType, + InvocationContext context) { + getMetricRegistry().counter(metricName(keyPattern, annotationType, context)).inc(); + } + + @Override + public void add(String keyPattern, long duration, Class annotationType, + InvocationContext context) { + getMetricRegistry().histogram(metricName(keyPattern, annotationType, context)).update(duration); + } + + @Override + public void insert(String keyPattern, LongSupplier gauge, Class annotationType, + InvocationContext context) { + String metricName = metricName(keyPattern, annotationType, context); + Gauge existingGauge = getMetricRegistry().getGauges().get(metricName); + if (existingGauge == null) { + Gauge newGauge = gauge::getAsLong; + getMetricRegistry().register(metricName, newGauge); + } + } + + private static String metricName(String keyPattern, Class annotationType, + InvocationContext context) { + return String.format(keyPattern, FaultToleranceCdiUtils.getFullAnnotatedMethodSignature(context, annotationType)); + } + + private MetricRegistry getMetricRegistry() { + if (metricRegistry == null) { + metricRegistry = CDI.current().select(MetricRegistry.class).get(); + } + return metricRegistry; + } +} diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/cdi/FaultToleranceCDIExtension.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/cdi/FaultToleranceCDIExtension.java index 10c680470f5..e16955bc3e4 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/cdi/FaultToleranceCDIExtension.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/cdi/FaultToleranceCDIExtension.java @@ -39,29 +39,20 @@ */ package fish.payara.microprofile.faulttolerance.cdi; -import fish.payara.microprofile.faulttolerance.interceptors.AsynchronousInterceptor; -import fish.payara.microprofile.faulttolerance.interceptors.BulkheadInterceptor; -import fish.payara.microprofile.faulttolerance.interceptors.CircuitBreakerInterceptor; -import fish.payara.microprofile.faulttolerance.interceptors.RetryInterceptor; -import fish.payara.microprofile.faulttolerance.interceptors.TimeoutInterceptor; -import fish.payara.microprofile.faulttolerance.validators.AsynchronousValidator; -import fish.payara.microprofile.faulttolerance.validators.BulkheadValidator; -import fish.payara.microprofile.faulttolerance.validators.CircuitBreakerValidator; -import fish.payara.microprofile.faulttolerance.validators.FallbackValidator; -import fish.payara.microprofile.faulttolerance.validators.RetryValidator; -import fish.payara.microprofile.faulttolerance.validators.TimeoutValidator; +import fish.payara.microprofile.faulttolerance.interceptors.FaultToleranceInterceptor; +import fish.payara.microprofile.faulttolerance.model.FaultToleranceBehaviour; import java.lang.annotation.Annotation; -import java.util.Set; +import java.lang.reflect.Method; + import javax.enterprise.event.Observes; -import javax.enterprise.inject.spi.AnnotatedMethod; -import javax.enterprise.inject.spi.AnnotatedType; +import javax.enterprise.inject.spi.Annotated; import javax.enterprise.inject.spi.BeanManager; import javax.enterprise.inject.spi.BeforeBeanDiscovery; import javax.enterprise.inject.spi.Extension; import javax.enterprise.inject.spi.ProcessAnnotatedType; import javax.enterprise.inject.spi.WithAnnotations; -import org.eclipse.microprofile.config.Config; -import org.eclipse.microprofile.config.ConfigProvider; +import javax.enterprise.inject.spi.configurator.AnnotatedMethodConfigurator; + import org.eclipse.microprofile.faulttolerance.Asynchronous; import org.eclipse.microprofile.faulttolerance.Bulkhead; import org.eclipse.microprofile.faulttolerance.CircuitBreaker; @@ -70,75 +61,50 @@ import org.eclipse.microprofile.faulttolerance.Timeout; /** - * CDI Extension that adds and validates the Fault Tolerance Annotations. + * CDI Extension that does the setup for FT interceptor handling. + * * @author Andrew Pielage + * @author Jan Bernitt */ public class FaultToleranceCDIExtension implements Extension { - + + /** + * The {@link FaultToleranceBehaviour} "instance" we use to dynamically mark methods at runtime that should be + * handled by the {@link FaultToleranceInterceptor} that handles all of the FT annotations. + */ + private static final Annotation MARKER = () -> FaultToleranceBehaviour.class; + void beforeBeanDiscovery(@Observes BeforeBeanDiscovery beforeBeanDiscovery, BeanManager beanManager) { - // Add each of the Fault Tolerance interceptors - beforeBeanDiscovery.addInterceptorBinding(Asynchronous.class); - AnnotatedType asynchronousInterceptor = - beanManager.createAnnotatedType(AsynchronousInterceptor.class); - beforeBeanDiscovery.addAnnotatedType(asynchronousInterceptor, AsynchronousInterceptor.class.getName()); - - beforeBeanDiscovery.addInterceptorBinding(Bulkhead.class); - AnnotatedType bulkheadInterceptor - = beanManager.createAnnotatedType(BulkheadInterceptor.class); - beforeBeanDiscovery.addAnnotatedType(bulkheadInterceptor, BulkheadInterceptor.class.getName()); - - beforeBeanDiscovery.addInterceptorBinding(CircuitBreaker.class); - AnnotatedType circuitBreakerInterceptor = - beanManager.createAnnotatedType(CircuitBreakerInterceptor.class); - beforeBeanDiscovery.addAnnotatedType(circuitBreakerInterceptor, CircuitBreakerInterceptor.class.getName()); - - beforeBeanDiscovery.addInterceptorBinding(Retry.class); - AnnotatedType retryInterceptor = beanManager.createAnnotatedType(RetryInterceptor.class); - beforeBeanDiscovery.addAnnotatedType(retryInterceptor, RetryInterceptor.class.getName()); - - beforeBeanDiscovery.addInterceptorBinding(Timeout.class); - AnnotatedType timeoutInterceptor = beanManager.createAnnotatedType(TimeoutInterceptor.class); - beforeBeanDiscovery.addAnnotatedType(timeoutInterceptor, TimeoutInterceptor.class.getName()); + beforeBeanDiscovery.addAnnotatedType(beanManager.createAnnotatedType(FaultToleranceInterceptor.class)); } - + void processAnnotatedType(@Observes @WithAnnotations({ Asynchronous.class, Bulkhead.class, CircuitBreaker.class, - Fallback.class, Retry.class, Timeout.class }) ProcessAnnotatedType processAnnotatedType, + Fallback.class, Retry.class, Timeout.class }) ProcessAnnotatedType processAnnotatedType, BeanManager beanManager) throws Exception { - AnnotatedType annotatedType = processAnnotatedType.getAnnotatedType(); - - // Validate the Fault Tolerance annotations for each annotated method - Set> annotatedMethods = annotatedType.getMethods(); - for (AnnotatedMethod annotatedMethod : annotatedMethods) { - validateMethodAnnotations(annotatedMethod); - } + mark(processAnnotatedType); } - + /** - * Helper method that validates the Fault Tolerance annotations for a given method. - * @param The annotated method type - * @param annotatedMethod The annotated method to validate - * @throws Exception + * Marks all {@link Method}s *affected* by FT annotation with the {@link FaultToleranceBehaviour} annotation which + * is handled by the {@link FaultToleranceInterceptor} which processes the FT annotations. + * + * @param processAnnotatedType type currently processed */ - private static void validateMethodAnnotations(AnnotatedMethod annotatedMethod) - throws ClassNotFoundException, NoSuchMethodException { - Config config = ConfigProvider.getConfig(); - - for (Annotation annotation : annotatedMethod.getAnnotations()) { - Class annotationType = annotation.annotationType(); - - if (annotationType == Asynchronous.class) { - AsynchronousValidator.validateAnnotation((Asynchronous) annotation, annotatedMethod); - } else if (annotationType == Bulkhead.class) { - BulkheadValidator.validateAnnotation((Bulkhead) annotation, annotatedMethod, config); - } else if (annotationType == CircuitBreaker.class) { - CircuitBreakerValidator.validateAnnotation((CircuitBreaker) annotation, annotatedMethod, config); - } else if (annotationType == Fallback.class) { - FallbackValidator.validateAnnotation((Fallback) annotation, annotatedMethod, config); - } else if (annotationType == Retry.class) { - RetryValidator.validateAnnotation((Retry) annotation, annotatedMethod, config); - } else if (annotationType == Timeout.class) { - TimeoutValidator.validateAnnotation((Timeout) annotation, annotatedMethod, config); + private static void mark(ProcessAnnotatedType processAnnotatedType) { + boolean markAllMethods = isAnnotaetdWithFaultToleranceAnnotations(processAnnotatedType.getAnnotatedType()); + for (AnnotatedMethodConfigurator methodConfigurator : processAnnotatedType.configureAnnotatedType().methods()) { + if (markAllMethods || isAnnotaetdWithFaultToleranceAnnotations(methodConfigurator.getAnnotated())) { + methodConfigurator.add(MARKER); } } } + + private static boolean isAnnotaetdWithFaultToleranceAnnotations(Annotated element) { + return element.isAnnotationPresent(Asynchronous.class) + || element.isAnnotationPresent(Bulkhead.class) + || element.isAnnotationPresent(CircuitBreaker.class) + || element.isAnnotationPresent(Fallback.class) + || element.isAnnotationPresent(Retry.class) + || element.isAnnotationPresent(Timeout.class); + } } diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/cdi/FaultToleranceCdiUtils.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/cdi/FaultToleranceCdiUtils.java index 9ca0247652a..7da6d1a1261 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/cdi/FaultToleranceCdiUtils.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/cdi/FaultToleranceCdiUtils.java @@ -40,13 +40,10 @@ package fish.payara.microprofile.faulttolerance.cdi; import java.lang.annotation.Annotation; -import java.util.Arrays; -import java.util.LinkedList; import java.util.Optional; -import java.util.Queue; +import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; -import javax.enterprise.inject.spi.BeanManager; import javax.interceptor.InvocationContext; import org.eclipse.microprofile.config.Config; @@ -55,57 +52,61 @@ * @author Andrew Pielage */ public class FaultToleranceCdiUtils { - + private static final Logger logger = Logger.getLogger(FaultToleranceCdiUtils.class.getName()); - + + public interface Stereotypes { + + boolean isStereotype(Class annotationType); + + Set getStereotypeDefinition(Class stereotype); + } + /** * Gets the annotation from the method that triggered the interceptor. * @param The annotation type to return - * @param beanManager The invoking interceptor's BeanManager + * @param sterotypes The invoking interceptor's context on sterotype annotations * @param annotationClass The class of the annotation to get - * @param invocationContext The context of the method invocation + * @param context The context of the method invocation * @return The annotation that triggered the interceptor. */ - public static A getAnnotation(BeanManager beanManager, Class annotationClass, - InvocationContext invocationContext) { - A annotation = null; - Class annotatedClass = getAnnotatedMethodClass(invocationContext, annotationClass); - + public static A getAnnotation(Stereotypes sterotypes, Class annotationClass, + InvocationContext context) { + Class annotatedClass = getAnnotatedMethodClass(context, annotationClass); logger.log(Level.FINER, "Attempting to get annotation {0} from {1}", - new String[]{annotationClass.getSimpleName(), invocationContext.getMethod().getName()}); + new String[]{annotationClass.getSimpleName(), context.getMethod().getName()}); // Try to get the annotation from the method, otherwise attempt to get it from the class - if (invocationContext.getMethod().isAnnotationPresent(annotationClass)) { + if (context.getMethod().isAnnotationPresent(annotationClass)) { logger.log(Level.FINER, "Annotation was directly present on the method"); - annotation = invocationContext.getMethod().getAnnotation(annotationClass); - } else { - if (annotatedClass.isAnnotationPresent(annotationClass)) { - logger.log(Level.FINER, "Annotation was directly present on the class"); - annotation = annotatedClass.getAnnotation(annotationClass); - } else { - logger.log(Level.FINER, "Annotation wasn't directly present on the method or class, " - + "checking stereotypes"); - // Account for Stereotypes - Queue annotations = new LinkedList<>(Arrays.asList(annotatedClass.getAnnotations())); - - while (!annotations.isEmpty()) { - Annotation a = annotations.remove(); - - if (a.annotationType().equals(annotationClass)) { + return context.getMethod().getAnnotation(annotationClass); + } + if (annotatedClass.isAnnotationPresent(annotationClass)) { + logger.log(Level.FINER, "Annotation was directly present on the class"); + return annotatedClass.getAnnotation(annotationClass); + } + if (sterotypes == null) { + return null; + } + logger.log(Level.FINER, "Annotation wasn't directly present on the method or class, checking stereotypes"); + // Account for Stereotypes + for (Annotation annotation : annotatedClass.getAnnotations()) { + Class annotationType = annotation.annotationType(); + if (annotationType == annotationClass) { + logger.log(Level.FINER, "Annotation was found in a stereotype"); + return annotationClass.cast(annotation); + } + if (sterotypes.isStereotype(annotationType)) { + for (Annotation metaAnnotation : sterotypes.getStereotypeDefinition(annotationType)) { + if (metaAnnotation.annotationType() == annotationClass) { logger.log(Level.FINER, "Annotation was found in a stereotype"); - annotation = annotationClass.cast(a); - break; - } - - if (beanManager.isStereotype(a.annotationType())) { - annotations.addAll(beanManager.getStereotypeDefinition(a.annotationType())); + return annotationClass.cast(metaAnnotation); } } } } - - return annotation; + return null; } - + /** * Gets overriding config parameter values if they're present. * @param The annotation type @@ -120,15 +121,15 @@ public static A getAnnotation(BeanManager beanManager, Cl public static Optional getOverrideValue(Config config, Class annotationClass, String parameterName, String annotatedMethodName, String annotatedClassCanonicalName, Class parameterType) { Optional value = Optional.empty(); - + String annotationName = annotationClass.getSimpleName(); - + // Check if there's a config override for the method if (config != null) { logger.log(Level.FINER, "Getting config override for annotated method..."); value = config.getOptionalValue(annotatedClassCanonicalName + "/" + annotatedMethodName + "/" + annotationName + "/" + parameterName, parameterType); - + // If there wasn't a config override for the method, check if there's one for the class if (!value.isPresent()) { logger.log(Level.FINER, "No config override for annotated method, getting config override for the " @@ -141,7 +142,7 @@ public static Optional getOverrideValue(Config conf logger.log(Level.FINER, "No config override for the annotated class, getting application wide " + "config override..."); value = config.getOptionalValue(annotationName + "/" + parameterName, parameterType); - + if (!value.isPresent()) { logger.log(Level.FINER, "No config overrides"); } @@ -150,53 +151,53 @@ public static Optional getOverrideValue(Config conf } else { logger.log(Level.FINE, "No config to get override parameters from."); } - + return value; } - + /** * Gets overriding config parameter values if they're present from an invocation context. * @param The annotation type * @param config The config to get the overriding parameter values from * @param annotationClass The annotation class * @param parameterName The name of the parameter to get the override value of - * @param invocationContext The context of the invoking request + * @param context The context of the invoking request * @param parameterType The type of the parameter to get the override value of * @return */ public static Optional getOverrideValue(Config config, Class annotationClass, - String parameterName, InvocationContext invocationContext, Class parameterType) { + String parameterName, InvocationContext context, Class parameterType) { Optional value = Optional.empty(); - + // Get the annotation, method, and class names String annotationName = annotationClass.getSimpleName(); - String annotatedMethodName = invocationContext.getMethod().getName(); + String annotatedMethodName = context.getMethod().getName(); String annotatedClassCanonicalName = getAnnotatedMethodClassCanonicalName( - getAnnotatedMethodClass(invocationContext, annotationClass)); - + getAnnotatedMethodClass(context, annotationClass)); + // Check if there's a config override if (config != null) { logger.log(Level.FINER, "Getting config override for annotated method..."); - + // Check if there's a config override for this specific method value = config.getOptionalValue(annotatedClassCanonicalName + "/" + annotatedMethodName + "/" + annotationName + "/" + parameterName, parameterType); - + if (!value.isPresent()) { logger.log(Level.FINER, "No config override for annotated method, checking if the method is " + "annotated directly..."); // If the method is annotated directly, check for a global level override and return - if (invocationContext.getMethod().getAnnotation(annotationClass) != null) { + if (context.getMethod().getAnnotation(annotationClass) != null) { logger.log(Level.FINER, "Method is annotated directly, checking for global override..."); // The only thing that should override a method-level annotation is a method or global-level config return checkForGlobalLevelOverride(annotationName, parameterName, parameterType, config); } - + // If the method wasn't annotated directly, check if there's an override for the class if (!value.isPresent()) { value = checkForClassLevelOverride(annotatedClassCanonicalName, annotationName, parameterName, parameterType, config); - + // If there wasn't a config override for the class, check if there's a global one if (!value.isPresent()) { value = checkForGlobalLevelOverride(annotationName, parameterName, parameterType, config); @@ -206,10 +207,10 @@ public static Optional getOverrideValue(Config conf } else { logger.log(Level.FINE, "No config to get override parameters from."); } - + return value; } - + /** * Gets overriding config enabled parameter value if it's present from an invocation context. * This follows a different priority logic than other parameter overrides. @@ -217,41 +218,41 @@ public static Optional getOverrideValue(Config conf * @param The annotation type * @param config The config to get the overriding enabled value from * @param annotationClass The annotation class - * @param invocationContext The context of the invoking request + * @param context The context of the invoking request * @return */ public static Optional getEnabledOverrideValue(Config config, Class annotationClass, - InvocationContext invocationContext) { + InvocationContext context) { Optional value = Optional.empty(); - + // Get the annotation, method, and class names String annotationName = annotationClass.getSimpleName(); - String annotatedMethodName = invocationContext.getMethod().getName(); + String annotatedMethodName = context.getMethod().getName(); String annotatedClassCanonicalName = getAnnotatedMethodClassCanonicalName( - getAnnotatedMethodClass(invocationContext, annotationClass)); - + getAnnotatedMethodClass(context, annotationClass)); + // Check if there's a config override if (config != null) { logger.log(Level.FINER, "Getting config override for annotated method..."); - + // Check if there's a config override for this specific method value = config.getOptionalValue(annotatedClassCanonicalName + "/" + annotatedMethodName + "/" + annotationName + "/" + "enabled", Boolean.class); - + if (!value.isPresent()) { logger.log(Level.FINER, "No config override for annotated method, checking if the method is " + "annotated directly..."); // If the method is annotated directly, check for a cless or global-level override - if (invocationContext.getMethod().getAnnotation(annotationClass) != null) { + if (context.getMethod().getAnnotation(annotationClass) != null) { logger.log(Level.FINER, "Method is annotated directly, checking for class or global override..."); checkAnnotatedMethodForEnabledOverride(annotatedClassCanonicalName, annotationName, config); } - + // If the method wasn't annotated directly, check if there's an override for the class if (!value.isPresent()) { value = checkForClassLevelOverride(annotatedClassCanonicalName, annotationName, "enabled", Boolean.class, config); - + // If there wasn't a config override for the class, check if there's a global one if (!value.isPresent()) { value = checkForGlobalLevelOverride(annotationName, "enabled", Boolean.class, config); @@ -261,10 +262,10 @@ public static Optional getEnabledOverrideValue(C } else { logger.log(Level.FINE, "No config to get override parameters from."); } - + return value; } - + /** * Helper method that logs whether an override was found. * @param value The Optional value to check if an override was found for @@ -277,7 +278,7 @@ private static void logOverride(Optional value, String level) { logger.log(Level.FINER, "No config overrides."); } } - + /** * Helper method that checks if a directly annotated method has a class or global-level override for the enabled * parameter. This gets its own method as the enabled parameter has a different override priority. @@ -290,14 +291,14 @@ private static Optional checkAnnotatedMethodForEnabledOverride(String a String annotationName, Config config) { Optional value = checkForClassLevelOverride(annotatedClassCanonicalName, annotationName, "enabled", Boolean.class, config); - + if (!value.isPresent()) { value = checkForGlobalLevelOverride(annotationName, "enabled", Boolean.class, config); } - + return value; } - + /** * Helper method that checks if there is an override for a parameter at the class level. * @param annotatedClassCanonicalName The canonical name of the annotated class @@ -314,10 +315,10 @@ private static Optional checkForClassLevelOverride(String annotatedClassC Optional value = config.getOptionalValue(annotatedClassCanonicalName + "/" + annotationName + "/" + parameterName, parameterType); logOverride(value, "Class-level"); - + return value; } - + /** * Helper method that checks if there is an override for a parameter at the global level. * @param annotationName The name of the annotation @@ -331,28 +332,28 @@ private static Optional checkForGlobalLevelOverride(String annotationName logger.log(Level.FINER, "No config override for annotated class, checking for global override."); Optional value = config.getOptionalValue(annotationName + "/" + parameterName, parameterType); logOverride(value, "Global"); - + return value; } - + /** * Returns either the CDI Proxy class (for cases where a class extends another without overriding the target method), * or the actual declaring class if the annotation isn't present on the proxy class. * @param The class of the annotation - * @param invocationContext The context of the method invocation + * @param context The context of the method invocation * @param annotationClass The class of the annotation * @return The class of the annotated method */ - public static Class getAnnotatedMethodClass(InvocationContext invocationContext, + public static Class getAnnotatedMethodClass(InvocationContext context, Class annotationClass) { - Class targetClass = invocationContext.getTarget().getClass(); - + Class targetClass = context.getTarget().getClass(); + if (targetClass.isAnnotationPresent(annotationClass)) { return targetClass; } - return invocationContext.getMethod().getDeclaringClass(); + return context.getMethod().getDeclaringClass(); } - + /** * Helper method that gets the canonical name of an annotated class. This is used to strip out any Weld proxy * naming that gets appended to the real class name. @@ -362,7 +363,7 @@ public static Class getAnnotatedMethodClass(Invocation */ public static String getAnnotatedMethodClassCanonicalName(Class annotatedClass) { String canonicalClassName = annotatedClass.getCanonicalName(); - + // If the class was a proxy from Weld, cut away the appended gubbins // Author Note: There's probably a better way of doing this... if (canonicalClassName.contains("$Proxy$_$$_WeldSubclass")) { @@ -370,17 +371,17 @@ public static String getAnnotatedMethodClassCanonicalName } return canonicalClassName; } - + /** * Gets the full method signature from an invocation context, stripping out any Weld proxy name gubbins. * @param The class of the annotation - * @param invocationContext The context of the method invocation + * @param context The context of the method invocation * @param annotationClass The class of the annotation * @return */ - public static String getFullAnnotatedMethodSignature(InvocationContext invocationContext, + public static String getFullAnnotatedMethodSignature(InvocationContext context, Class annotationClass) { - return getAnnotatedMethodClassCanonicalName(getAnnotatedMethodClass(invocationContext, annotationClass)) + "." - + invocationContext.getMethod().getName(); + return getAnnotatedMethodClassCanonicalName(getAnnotatedMethodClass(context, annotationClass)) + "." + + context.getMethod().getName(); } } diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/AsynchronousInterceptor.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/AsynchronousInterceptor.java index 3726105af01..ec1067762e0 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/AsynchronousInterceptor.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/AsynchronousInterceptor.java @@ -39,31 +39,23 @@ */ package fish.payara.microprofile.faulttolerance.interceptors; -import fish.payara.microprofile.faulttolerance.FaultToleranceService; -import fish.payara.microprofile.faulttolerance.cdi.FaultToleranceCdiUtils; import fish.payara.microprofile.faulttolerance.interceptors.fallback.FallbackPolicy; import java.io.Serializable; -import java.util.concurrent.Callable; +import java.util.concurrent.CompletionStage; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.logging.Level; -import java.util.logging.Logger; import javax.annotation.Priority; -import javax.enterprise.concurrent.ManagedExecutorService; -import javax.enterprise.inject.spi.BeanManager; -import javax.inject.Inject; import javax.interceptor.AroundInvoke; import javax.interceptor.Interceptor; import javax.interceptor.InvocationContext; -import org.eclipse.microprofile.config.Config; -import org.eclipse.microprofile.config.ConfigProvider; +import javax.naming.NamingException; + import org.eclipse.microprofile.faulttolerance.Asynchronous; import org.eclipse.microprofile.faulttolerance.Fallback; import org.eclipse.microprofile.faulttolerance.exceptions.FaultToleranceException; -import org.glassfish.api.invocation.InvocationManager; -import org.glassfish.internal.api.Globals; /** * Interceptor for the Fault Tolerance Asynchronous Annotation. Also contains the wrapper class for the Future outcome. @@ -73,75 +65,65 @@ @Interceptor @Asynchronous @Priority(Interceptor.Priority.PLATFORM_AFTER) -public class AsynchronousInterceptor implements Serializable { +public class AsynchronousInterceptor extends BaseFaultToleranceInterceptor implements Serializable { + + public AsynchronousInterceptor() { + super(Asynchronous.class, false); + } - private static final Logger logger = Logger.getLogger(AsynchronousInterceptor.class.getName()); - - @Inject - BeanManager beanManager; - @AroundInvoke - public Object intercept(InvocationContext invocationContext) throws Exception { - Object proceededInvocationContext = null; - - // Get the configured ManagedExecutorService from the Fault Tolerance Service - FaultToleranceService faultToleranceService = Globals.getDefaultBaseServiceLocator() - .getService(FaultToleranceService.class); - ManagedExecutorService managedExecutorService = faultToleranceService.getManagedExecutorService(); - - InvocationManager invocationManager = Globals.getDefaultBaseServiceLocator() - .getService(InvocationManager.class); - - Config config = null; - try { - config = ConfigProvider.getConfig(); - } catch (IllegalArgumentException ex) { - logger.log(Level.INFO, "No config could be found", ex); - } - + public Object intercept(InvocationContext context) throws Exception { + Object resultValue = null; + try { - String appName = faultToleranceService.getApplicationName(invocationManager, invocationContext); - // Attempt to proceed the InvocationContext with Asynchronous semantics if Fault Tolerance is enabled for // this method - if (faultToleranceService.isFaultToleranceEnabled(appName, config) - && (FaultToleranceCdiUtils.getEnabledOverrideValue( - config, Asynchronous.class, invocationContext) - .orElse(Boolean.TRUE))) { - Callable callable = () -> invocationContext.proceed(); - logger.log(Level.FINER, "Proceeding invocation asynchronously"); - proceededInvocationContext = new FutureDelegator(managedExecutorService.submit(callable)); + if (getConfig().isEnabled(context) && getConfig().isEnabled(Asynchronous.class, context)) { + resultValue = asynchronous(context); } else { // If fault tolerance isn't enabled, just proceed as normal - logger.log(Level.FINE, "Fault Tolerance not enabled for {0}, proceeding normally without asynchronous.", - faultToleranceService.getApplicationName(invocationManager, invocationContext)); - proceededInvocationContext = invocationContext.proceed(); + logger.log(Level.FINE, "Fault Tolerance not enabled, proceeding normally without asynchronous."); + resultValue = context.proceed(); } } catch (Exception ex) { // If an exception was thrown, check if the method is annotated with @Fallback // We should only get here if executing synchronously, as the exception wouldn't get thrown in this thread - Fallback fallback = FaultToleranceCdiUtils.getAnnotation(beanManager, Fallback.class, invocationContext); - + Fallback fallback = getConfig().getAnnotation(Fallback.class, context); + // If the method was annotated with Fallback and the annotation is enabled, attempt it, otherwise just // propagate the exception upwards - if (fallback != null && (FaultToleranceCdiUtils.getEnabledOverrideValue( - config, Fallback.class, invocationContext) - .orElse(Boolean.TRUE))) { + if (fallback != null && getConfig().isEnabled(Fallback.class, context)) { logger.log(Level.FINE, "Fallback annotation found on method - falling back from Asynchronous"); - FallbackPolicy fallbackPolicy = new FallbackPolicy(fallback, config, invocationContext); - proceededInvocationContext = fallbackPolicy.fallback(invocationContext, ex); + FallbackPolicy fallbackPolicy = new FallbackPolicy(fallback, getConfig(), getExecution(), getMetrics(), context); + resultValue = fallbackPolicy.fallback(context, ex); } else { throw ex; } } - - return proceededInvocationContext; + + return resultValue; + } + + private Object asynchronous(InvocationContext context) throws Exception, NamingException { + Class returnType = context.getMethod().getReturnType(); + if (returnType == CompletionStage.class) { + logger.log(Level.FINER, "Proceeding invocation asynchronously"); + //TODO + return context.proceed(); + } + if (returnType == Future.class) { + logger.log(Level.FINER, "Proceeding invocation asynchronously"); + return new FutureDelegator(getExecution().runAsynchronous(context)); + } + logger.log(Level.SEVERE, "Unsupported return type for @Asynchronous annotated method: " + returnType + + ", proceeding normally without asynchronous."); + return context.proceed(); } /** * Wrapper class for the Future object */ - class FutureDelegator implements Future { + static class FutureDelegator implements Future { private final Future future; @@ -166,46 +148,41 @@ public boolean isDone() { @Override public Object get() throws InterruptedException, ExecutionException { - Object proceededInvocation; - try { - proceededInvocation = future.get(); - + Object resultValue = future.get(); + // If the result of future.get() is still a future, get it again - if (proceededInvocation instanceof Future) { - Future tempFuture = (Future) proceededInvocation; - proceededInvocation = tempFuture.get(); + if (resultValue instanceof Future) { + Future tempFuture = (Future) resultValue; + return tempFuture.get(); } + return resultValue; } catch (InterruptedException | ExecutionException ex) { if (ex.getCause() instanceof FaultToleranceException) { throw (FaultToleranceException) ex.getCause(); } throw ex; } - - return proceededInvocation; } @Override public Object get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { - Object proceededInvocation; - try { - proceededInvocation = future.get(timeout, unit); - + Object resultValue = future.get(timeout, unit); + // If the result of future.get() is still a future, get it again - if (proceededInvocation instanceof Future) { - Future tempFuture = (Future) proceededInvocation; - proceededInvocation = tempFuture.get(timeout, unit); + if (resultValue instanceof Future) { + Future tempFuture = (Future) resultValue; + return tempFuture.get(timeout, unit); } + return resultValue; } catch (InterruptedException | ExecutionException | TimeoutException ex) { if (ex.getCause() instanceof FaultToleranceException) { throw new ExecutionException(ex.getCause()); } throw ex; } - - return proceededInvocation; } + } } diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/BaseFaultToleranceInterceptor.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/BaseFaultToleranceInterceptor.java new file mode 100644 index 00000000000..31f69cf061c --- /dev/null +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/BaseFaultToleranceInterceptor.java @@ -0,0 +1,97 @@ +package fish.payara.microprofile.faulttolerance.interceptors; + +import java.lang.annotation.Annotation; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.inject.Inject; +import javax.interceptor.AroundInvoke; +import javax.interceptor.InvocationContext; + +import org.eclipse.microprofile.faulttolerance.Bulkhead; +import org.eclipse.microprofile.faulttolerance.CircuitBreaker; +import org.eclipse.microprofile.faulttolerance.Fallback; +import org.eclipse.microprofile.faulttolerance.Retry; + +import fish.payara.microprofile.faulttolerance.FaultToleranceConfig; +import fish.payara.microprofile.faulttolerance.FaultToleranceMetrics; +import fish.payara.microprofile.faulttolerance.FaultToleranceExecution; +import fish.payara.microprofile.faulttolerance.interceptors.fallback.FallbackPolicy; + +public abstract class BaseFaultToleranceInterceptor { + + protected final Logger logger; + private final String type; + private final Class annotationType; + private final boolean throwExceptionForRetry; + private FaultToleranceConfig config = FaultToleranceConfig.ANNOTATED; + private FaultToleranceExecution execution; + private FaultToleranceMetrics metrics; + + protected BaseFaultToleranceInterceptor(Class annotationType, boolean throwExceptionForRetry) { + this.type = annotationType.getSimpleName(); + this.annotationType = annotationType; + this.throwExceptionForRetry = throwExceptionForRetry; + this.logger = Logger.getLogger(getClass().getName()); + } + + public void setConfig(FaultToleranceConfig behaviour) { + this.config = behaviour; + } + + public FaultToleranceConfig getConfig() { + return config; + } + + public FaultToleranceMetrics getMetrics() { + return metrics; + } + + public FaultToleranceExecution getExecution() { + return execution; + } + + @AroundInvoke + public Object intercept(InvocationContext context) throws Exception { + Object resultValue = null; + + try { + // Attempt to proceed the InvocationContext with FT semantics if FT is enabled for this method + if (getConfig().isEnabled(context) && getConfig().isEnabled(annotationType, context)) { + // Only increment the invocations metric if the Retry, Bulkhead, and CircuitBreaker annotations aren't present + if (!getConfig().isAnnotationPresent(Bulkhead.class, context) + && !getConfig().isAnnotationPresent(Retry.class, context) + && !getConfig().isAnnotationPresent(CircuitBreaker.class, context)) { + getMetrics().incrementInvocationsTotal(annotationType, context); + } + + logger.log(Level.FINER, "Proceeding invocation with " + type + " semantics"); + resultValue = null; //TODO call FT specific method + } else { + // If fault tolerance isn't enabled, just proceed as normal + logger.log(Level.FINE, "Fault Tolerance not enabled, proceeding normally without " + + type + "."); + resultValue = context.proceed(); + } + } catch (Exception ex) { + if (throwExceptionForRetry && getConfig().isAnnotationPresent(Retry.class, context)) { + logger.log(Level.FINE, "Retry annotation found on method, propagating error upwards."); + throw ex; + } + Fallback fallback = getConfig().getAnnotation(Fallback.class, context); + + // Only fall back if the annotation hasn't been disabled + if (fallback != null && getConfig().isEnabled(Fallback.class, context)) { + logger.log(Level.FINE, "Fallback annotation found on method, and no Retry annotation - " + + "falling back from " + type); + FallbackPolicy fallbackPolicy = new FallbackPolicy(fallback, getConfig(), getExecution(), getMetrics(), context); + resultValue = fallbackPolicy.fallback(context, ex); + } else { + // Increment the failure counter metric + getMetrics().incrementInvocationsFailedTotal(annotationType, context); + throw ex; + } + } + return resultValue; + } +} diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/BulkheadInterceptor.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/BulkheadInterceptor.java index 8ff6c73bdae..b163f77ea0e 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/BulkheadInterceptor.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/BulkheadInterceptor.java @@ -40,328 +40,214 @@ package fish.payara.microprofile.faulttolerance.interceptors; import fish.payara.microprofile.faulttolerance.interceptors.fallback.FallbackPolicy; -import fish.payara.microprofile.faulttolerance.FaultToleranceService; -import fish.payara.microprofile.faulttolerance.cdi.FaultToleranceCdiUtils; +import fish.payara.microprofile.faulttolerance.state.BulkheadSemaphore; import java.io.Serializable; -import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; import java.util.logging.Level; -import java.util.logging.Logger; import javax.annotation.Priority; -import javax.enterprise.inject.spi.BeanManager; -import javax.inject.Inject; import javax.interceptor.AroundInvoke; import javax.interceptor.Interceptor; import javax.interceptor.InvocationContext; -import fish.payara.notification.requesttracing.RequestTraceSpan; -import javax.enterprise.inject.spi.CDI; -import org.eclipse.microprofile.config.Config; -import org.eclipse.microprofile.config.ConfigProvider; import org.eclipse.microprofile.faulttolerance.Asynchronous; import org.eclipse.microprofile.faulttolerance.Bulkhead; import org.eclipse.microprofile.faulttolerance.Fallback; import org.eclipse.microprofile.faulttolerance.Retry; import org.eclipse.microprofile.faulttolerance.exceptions.BulkheadException; -import org.eclipse.microprofile.metrics.Gauge; -import org.eclipse.microprofile.metrics.MetricRegistry; -import org.glassfish.api.invocation.InvocationManager; -import org.glassfish.internal.api.Globals; /** * Interceptor for the Fault Tolerance Bulkhead Annotation. * * @author Andrew Pielage + * @author Jan Bernitt (2.0 update) */ @Interceptor @Bulkhead @Priority(Interceptor.Priority.PLATFORM_AFTER + 10) -public class BulkheadInterceptor implements Serializable { - - private static final Logger logger = Logger.getLogger(BulkheadInterceptor.class.getName()); - - @Inject - private BeanManager beanManager; - +public class BulkheadInterceptor extends BaseFaultToleranceInterceptor implements Serializable { + + public BulkheadInterceptor() { + super(Bulkhead.class, true); + } + @AroundInvoke - public Object intercept(InvocationContext invocationContext) throws Exception { - Object proceededInvocationContext = null; - - FaultToleranceService faultToleranceService = - Globals.getDefaultBaseServiceLocator().getService(FaultToleranceService.class); - InvocationManager invocationManager = Globals.getDefaultBaseServiceLocator() - .getService(InvocationManager.class); - - MetricRegistry metricRegistry = CDI.current().select(MetricRegistry.class).get(); - String fullMethodSignature = FaultToleranceCdiUtils.getFullAnnotatedMethodSignature(invocationContext, - Bulkhead.class); - - Config config = null; - - try { - config = ConfigProvider.getConfig(); - } catch (IllegalArgumentException ex) { - logger.log(Level.INFO, "No config could be found", ex); - } - + public Object intercept(InvocationContext context) throws Exception { + Object resultValue = null; + try { - String appName = faultToleranceService.getApplicationName(invocationManager, invocationContext); - // Attempt to proceed the InvocationContext with Asynchronous semantics if Fault Tolerance is enabled for this // method - if (faultToleranceService.isFaultToleranceEnabled(appName, config) - && (FaultToleranceCdiUtils.getEnabledOverrideValue( - config, Bulkhead.class, invocationContext) - .orElse(Boolean.TRUE))) { - if (faultToleranceService.areFaultToleranceMetricsEnabled(appName, config)) { + if (getConfig().isEnabled(context) && getConfig().isEnabled(Bulkhead.class, context)) { + if (getConfig().isMetricsEnabled(context)) { // Only increment the invocations metric if the Retry annotation isn't present - if (FaultToleranceCdiUtils.getAnnotation(beanManager, Retry.class, invocationContext) == null) { - faultToleranceService.incrementCounterMetric(metricRegistry, - "ft." + fullMethodSignature + ".invocations.total", appName, config); + if (getConfig().getAnnotation(Retry.class, context) == null) { + getMetrics().incrementInvocationsTotal(Bulkhead.class, context); } } - - + + logger.log(Level.FINER, "Proceeding invocation with bulkhead semantics"); - proceededInvocationContext = bulkhead(invocationContext); + resultValue = bulkhead(context); } else { // If fault tolerance isn't enabled, just proceed as normal - logger.log(Level.FINE, "Fault Tolerance not enabled for {0}, proceeding normally without bulkhead.", - faultToleranceService.getApplicationName(invocationManager, invocationContext)); - proceededInvocationContext = invocationContext.proceed(); + logger.log(Level.FINE, "Fault Tolerance not enabled, proceeding normally without bulkhead."); + resultValue = context.proceed(); } } catch (Exception ex) { - Retry retry = FaultToleranceCdiUtils.getAnnotation(beanManager, Retry.class, invocationContext); - + Retry retry = getConfig().getAnnotation(Retry.class, context); + if (retry != null) { logger.log(Level.FINE, "Retry annotation found on method, propagating error upwards."); throw ex; } // If an exception was thrown, check if the method is annotated with @Fallback - Fallback fallback = FaultToleranceCdiUtils.getAnnotation(beanManager, Fallback.class, - invocationContext); + Fallback fallback = getConfig().getAnnotation(Fallback.class, context); // If the method was annotated with Fallback and the annotation is enabled, attempt it, otherwise just // propagate the exception upwards - if (fallback != null && (FaultToleranceCdiUtils.getEnabledOverrideValue( - config, Fallback.class, invocationContext) - .orElse(Boolean.TRUE))) { + if (fallback != null && getConfig().isEnabled(Fallback.class, context)) { logger.log(Level.FINE, "Fallback annotation found on method, and no Retry annotation - " + "falling back from Bulkhead"); - FallbackPolicy fallbackPolicy = new FallbackPolicy(fallback, config, invocationContext); - proceededInvocationContext = fallbackPolicy.fallback(invocationContext, ex); + FallbackPolicy fallbackPolicy = new FallbackPolicy(fallback, getConfig(), getExecution(), getMetrics(), context); + resultValue = fallbackPolicy.fallback(context, ex); } else { logger.log(Level.FINE, "Fallback annotation not found on method, propagating error upwards.", ex); - + // Increment the failure counter metric - faultToleranceService.incrementCounterMetric(metricRegistry, - "ft." + fullMethodSignature + ".invocations.failed.total", - faultToleranceService.getApplicationName(invocationManager, invocationContext), - config); - + getMetrics().incrementInvocationsFailedTotal(Bulkhead.class, context); throw ex; } } - - return proceededInvocationContext; + + return resultValue; } - + /** * Proceeds the context under Bulkhead semantics. - * @param invocationContext The context to proceed. + * @param context The context to proceed. * @return The outcome of the invocationContext * @throws Exception */ - private Object bulkhead(InvocationContext invocationContext) throws Exception { - Object proceededInvocationContext = null; - - FaultToleranceService faultToleranceService = - Globals.getDefaultBaseServiceLocator().getService(FaultToleranceService.class); - Bulkhead bulkhead = FaultToleranceCdiUtils.getAnnotation(beanManager, Bulkhead.class, invocationContext); - - Config config = null; - - try { - config = ConfigProvider.getConfig(); - } catch (IllegalArgumentException ex) { - logger.log(Level.INFO, "No config could be found", ex); - } - - int value = FaultToleranceCdiUtils.getOverrideValue( - config, Bulkhead.class, "value", invocationContext, Integer.class) - .orElse(bulkhead.value()); - int waitingTaskQueue = FaultToleranceCdiUtils.getOverrideValue( - config, Bulkhead.class, "waitingTaskQueue", invocationContext, Integer.class) - .orElse(bulkhead.waitingTaskQueue()); - - InvocationManager invocationManager = Globals.getDefaultBaseServiceLocator() - .getService(InvocationManager.class); - - String appName = faultToleranceService.getApplicationName(invocationManager, invocationContext); - - - MetricRegistry metricRegistry = CDI.current().select(MetricRegistry.class).get(); - String fullMethodSignature = FaultToleranceCdiUtils.getFullAnnotatedMethodSignature(invocationContext, - Bulkhead.class); - - Semaphore bulkheadExecutionSemaphore = faultToleranceService.getBulkheadExecutionSemaphore(appName, - invocationContext.getTarget(), invocationContext.getMethod(), value); - - if (faultToleranceService.areFaultToleranceMetricsEnabled(appName, config)) { - Gauge concurrentExecutionsGauge = metricRegistry.getGauges() - .get("ft." + fullMethodSignature + ".bulkhead.concurrentExecutions"); - - // Register a bulkhead concurrent executions metric if there isn't one - if (concurrentExecutionsGauge == null) { - concurrentExecutionsGauge = () -> getConcurrentExecutionsCount(value, bulkheadExecutionSemaphore); - - metricRegistry.register("ft." + fullMethodSignature + ".bulkhead.concurrentExecutions", - concurrentExecutionsGauge); - } + private Object bulkhead(InvocationContext context) throws Exception { + Object resultValue = null; + + Bulkhead bulkhead = getConfig().getAnnotation(Bulkhead.class, context); + + BulkheadSemaphore bulkheadExecutionSemaphore = getExecution().getExecutionSemaphoreOf(getConfig().value(bulkhead, context), context); + + if (getConfig().isMetricsEnabled(context)) { + getMetrics().insertBulkheadConcurrentExecutions(bulkheadExecutionSemaphore::acquiredPermits, context); } - + // If the Asynchronous annotation is present, use threadpool style, otherwise use semaphore style - if (FaultToleranceCdiUtils.getAnnotation(beanManager, Asynchronous.class, invocationContext) != null) { - Semaphore bulkheadExecutionQueueSemaphore = faultToleranceService.getBulkheadExecutionQueueSemaphore(appName, - invocationContext.getTarget(), invocationContext.getMethod(), waitingTaskQueue); - - if (faultToleranceService.areFaultToleranceMetricsEnabled(appName, config)) { - Gauge waitingQueueGauge = metricRegistry.getGauges() - .get("ft." + fullMethodSignature + ".bulkhead.waitingQueue.population"); - - // Register a bulkhead queue metric if there isn't one - if (waitingQueueGauge == null) { - waitingQueueGauge = () -> getWaitingQueueCount(waitingTaskQueue, bulkheadExecutionQueueSemaphore); - - metricRegistry.register("ft." + fullMethodSignature + ".bulkhead.waitingQueue.population", - waitingQueueGauge); - } + if (getConfig().getAnnotation(Asynchronous.class, context) != null) { + BulkheadSemaphore bulkheadExecutionQueueSemaphore = getExecution().getWaitingQueueSemaphoreOf(getConfig().waitingTaskQueue(bulkhead, context), context); + + if (getConfig().isMetricsEnabled(context)) { + getMetrics().insertBulkheadWaitingQueuePopulation(bulkheadExecutionQueueSemaphore::acquiredPermits, context); } - + // Start measuring the queue duration for MP Metrics long queueStartTime = System.nanoTime(); - + // Check if there are any free permits for concurrent execution if (!bulkheadExecutionSemaphore.tryAcquire(0, TimeUnit.SECONDS)) { logger.log(Level.FINER, "Attempting to acquire bulkhead queue semaphore."); // If there aren't any free permits, see if there are any free queue permits if (bulkheadExecutionQueueSemaphore.tryAcquire(0, TimeUnit.SECONDS)) { logger.log(Level.FINER, "Acquired bulkhead queue semaphore."); - + // If there is a free queue permit, queue for an executor permit try { logger.log(Level.FINER, "Attempting to acquire bulkhead execution semaphore."); - faultToleranceService.startFaultToleranceSpan(new RequestTraceSpan("obtainBulkheadSemaphore"), - invocationManager, invocationContext); + getExecution().startTrace("obtainBulkheadSemaphore", context); try { bulkheadExecutionSemaphore.acquire(); } finally { // Make sure we end the trace right here - faultToleranceService.endFaultToleranceSpan(); - + getExecution().endTrace(); + // Record the queue time for MP Metrics - faultToleranceService.updateHistogramMetric(metricRegistry, - "ft." + fullMethodSignature + ".bulkhead.waiting.duration", - System.nanoTime() - queueStartTime, - appName, config); + getMetrics().addBulkheadWaitingDuration(System.nanoTime() - queueStartTime, context); } - + logger.log(Level.FINER, "Acquired bulkhead queue semaphore."); - + // Release the queue permit bulkheadExecutionQueueSemaphore.release(); - + // Incremement the MP Metrics callsAccepted counter - faultToleranceService.incrementCounterMetric(metricRegistry, - "ft." + fullMethodSignature + ".bulkhead.callsAccepted.total", appName, config); - + getMetrics().incrementBulkheadCallsAcceptedTotal(context); + // Start measuring the execution duration for MP Metrics long executionStartTime = System.nanoTime(); - + // Proceed the invocation and wait for the response try { logger.log(Level.FINER, "Proceeding bulkhead context"); - proceededInvocationContext = invocationContext.proceed(); + resultValue = context.proceed(); } catch (Exception ex) { logger.log(Level.FINE, "Exception proceeding Bulkhead context", ex); - + // Generic catch, as we need to release the semaphore permits bulkheadExecutionSemaphore.release(); bulkheadExecutionQueueSemaphore.release(); - + // Record the execution time for MP Metrics - faultToleranceService.updateHistogramMetric(metricRegistry, - "ft." + fullMethodSignature + ".bulkhead.executionDuration", - System.nanoTime() - executionStartTime, - appName, config); - + getMetrics().addBulkheadExecutionDuration(System.nanoTime() - executionStartTime, context); + // Let the exception propagate further up - we just want to release the semaphores throw ex; } - + // Record the execution time for MP Metrics - faultToleranceService.updateHistogramMetric(metricRegistry, - "ft." + fullMethodSignature + ".bulkhead.executionDuration", - System.nanoTime() - executionStartTime, - appName, config); - + getMetrics().addBulkheadExecutionDuration(System.nanoTime() - executionStartTime, context); + // Release the execution permit bulkheadExecutionSemaphore.release(); } catch (InterruptedException ex) { // Incremement the MP Metrics callsRejected counter - faultToleranceService.incrementCounterMetric(metricRegistry, - "ft." + fullMethodSignature + ".bulkhead.callsRejected.total", appName, config); - + getMetrics().incrementBulkheadCallsRejectedTotal(context); + logger.log(Level.INFO, "Interrupted acquiring bulkhead semaphore", ex); throw new BulkheadException(ex); } } else { // Incremement the MP Metrics callsRejected counter - faultToleranceService.incrementCounterMetric(metricRegistry, - "ft." + fullMethodSignature + ".bulkhead.callsRejected.total", appName, config); - + getMetrics().incrementBulkheadCallsRejectedTotal(context); + throw new BulkheadException("No free work or queue permits."); } } else { // Incremement the MP Metrics callsAccepted counter - faultToleranceService.incrementCounterMetric(metricRegistry, - "ft." + fullMethodSignature + ".bulkhead.callsAccepted.total", appName, config); - + getMetrics().incrementBulkheadCallsAcceptedTotal(context); + // Record the queue time for MP Metrics - faultToleranceService.updateHistogramMetric(metricRegistry, - "ft." + fullMethodSignature + ".bulkhead.waiting.duration", - System.nanoTime() - queueStartTime, - appName, config); - + getMetrics().addBulkheadWaitingDuration(System.nanoTime() - queueStartTime, context); + // Start measuring the execution duration for MP Metrics long executionStartTime = System.nanoTime(); - + // Proceed the invocation and wait for the response try { logger.log(Level.FINER, "Proceeding bulkhead context"); - proceededInvocationContext = invocationContext.proceed(); + resultValue = context.proceed(); } catch (Exception ex) { logger.log(Level.FINE, "Exception proceeding Bulkhead context", ex); // Generic catch, as we need to release the semaphore permits bulkheadExecutionSemaphore.release(); - + // Record the execution time for MP Metrics - faultToleranceService.updateHistogramMetric(metricRegistry, - "ft." + fullMethodSignature + ".bulkhead.executionDuration", - System.nanoTime() - executionStartTime, - appName, config); - + getMetrics().addBulkheadExecutionDuration(System.nanoTime() - executionStartTime, context); + // Let the exception propagate further up - we just want to release the semaphores throw ex; } - + // Record the execution time for MP Metrics - faultToleranceService.updateHistogramMetric(metricRegistry, - "ft." + fullMethodSignature + ".bulkhead.executionDuration", - System.nanoTime() - executionStartTime, - appName, config); - + getMetrics().addBulkheadExecutionDuration(System.nanoTime() - executionStartTime, context); + // Release the permit bulkheadExecutionSemaphore.release(); } @@ -369,57 +255,41 @@ private Object bulkhead(InvocationContext invocationContext) throws Exception { // Try to get an execution permit if (bulkheadExecutionSemaphore.tryAcquire(0, TimeUnit.SECONDS)) { // Incremement the MP Metrics callsAccepted counter - faultToleranceService.incrementCounterMetric(metricRegistry, - "ft." + fullMethodSignature + ".bulkhead.callsAccepted.total", appName, config); - + getMetrics().incrementBulkheadCallsAcceptedTotal(context); + // Start measuring the execution duration for MP Metrics long executionStartTime = System.nanoTime(); - + // Proceed the invocation and wait for the response try { logger.log(Level.FINER, "Proceeding bulkhead context"); - proceededInvocationContext = invocationContext.proceed(); + resultValue = context.proceed(); } catch (Exception ex) { logger.log(Level.FINE, "Exception proceeding Bulkhead context", ex); - + // Generic catch, as we need to release the semaphore permits bulkheadExecutionSemaphore.release(); // Record the execution time for MP Metrics - faultToleranceService.updateHistogramMetric(metricRegistry, - "ft." + fullMethodSignature + ".bulkhead.executionDuration", - System.nanoTime() - executionStartTime, - appName, config); - + getMetrics().addBulkheadExecutionDuration(System.nanoTime() - executionStartTime, context); + // Let the exception propagate further up - we just want to release the semaphores throw ex; } - + // Record the execution time for MP Metrics - faultToleranceService.updateHistogramMetric(metricRegistry, - "ft." + fullMethodSignature + ".bulkhead.executionDuration", - System.nanoTime() - executionStartTime, - appName, config); - + getMetrics().addBulkheadExecutionDuration(System.nanoTime() - executionStartTime, context); + // Release the permit bulkheadExecutionSemaphore.release(); } else { // Incremement the MP Metrics callsRejected counter - faultToleranceService.incrementCounterMetric(metricRegistry, - "ft." + fullMethodSignature + ".bulkhead.callsRejected.total", appName, config); - + getMetrics().incrementBulkheadCallsRejectedTotal(context); + throw new BulkheadException("No free work permits."); } } - - return proceededInvocationContext; - } - - private static Long getConcurrentExecutionsCount(int bulkheadValue, Semaphore bulkheadExecutionSemaphore) { - return ((Number) (bulkheadValue - bulkheadExecutionSemaphore.availablePermits())).longValue(); - } - - private static Long getWaitingQueueCount(int waitingTaskQueue, Semaphore bulkheadExecutionQueueSemaphore) { - return ((Number) (waitingTaskQueue - bulkheadExecutionQueueSemaphore.availablePermits())).longValue(); + + return resultValue; } } diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/CircuitBreakerInterceptor.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/CircuitBreakerInterceptor.java index 72750a8032d..fa3c2bcce7b 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/CircuitBreakerInterceptor.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/CircuitBreakerInterceptor.java @@ -39,41 +39,22 @@ */ package fish.payara.microprofile.faulttolerance.interceptors; -import fish.payara.microprofile.faulttolerance.FaultToleranceService; -import fish.payara.microprofile.faulttolerance.cdi.FaultToleranceCdiUtils; import fish.payara.microprofile.faulttolerance.interceptors.fallback.FallbackPolicy; import fish.payara.microprofile.faulttolerance.state.CircuitBreakerState; -import org.eclipse.microprofile.config.Config; -import org.eclipse.microprofile.config.ConfigProvider; import org.eclipse.microprofile.faulttolerance.Bulkhead; import org.eclipse.microprofile.faulttolerance.CircuitBreaker; import org.eclipse.microprofile.faulttolerance.Fallback; import org.eclipse.microprofile.faulttolerance.Retry; import org.eclipse.microprofile.faulttolerance.exceptions.CircuitBreakerOpenException; -import org.eclipse.microprofile.metrics.Gauge; -import org.eclipse.microprofile.metrics.MetricRegistry; -import org.glassfish.api.invocation.InvocationManager; -import org.glassfish.internal.api.Globals; import javax.annotation.Priority; -import javax.enterprise.concurrent.ManagedScheduledExecutorService; -import javax.enterprise.inject.spi.BeanManager; -import javax.enterprise.inject.spi.CDI; -import javax.inject.Inject; import javax.interceptor.AroundInvoke; import javax.interceptor.Interceptor; import javax.interceptor.InvocationContext; -import javax.naming.NamingException; import java.io.Serializable; import java.time.Duration; import java.time.temporal.ChronoUnit; -import java.util.ArrayList; -import java.util.List; -import java.util.NoSuchElementException; -import java.util.Optional; -import java.util.concurrent.TimeUnit; import java.util.logging.Level; -import java.util.logging.Logger; /** * @@ -82,186 +63,93 @@ @Interceptor @CircuitBreaker @Priority(Interceptor.Priority.PLATFORM_AFTER + 15) -public class CircuitBreakerInterceptor implements Serializable { +public class CircuitBreakerInterceptor extends BaseFaultToleranceInterceptor implements Serializable { - private static final Logger logger = Logger.getLogger(CircuitBreakerInterceptor.class.getName()); - - @Inject - private BeanManager beanManager; + public CircuitBreakerInterceptor() { + super(CircuitBreaker.class, true); + } @AroundInvoke - public Object intercept(InvocationContext invocationContext) throws Exception { - Object proceededInvocationContext = null; - - FaultToleranceService faultToleranceService = - Globals.getDefaultBaseServiceLocator().getService(FaultToleranceService.class); - InvocationManager invocationManager = Globals.getDefaultBaseServiceLocator() - .getService(InvocationManager.class); - - MetricRegistry metricRegistry = CDI.current().select(MetricRegistry.class).get(); - String fullMethodSignature = FaultToleranceCdiUtils.getFullAnnotatedMethodSignature(invocationContext, - CircuitBreaker.class); - - Config config = null; - try { - config = ConfigProvider.getConfig(); - } catch (IllegalArgumentException ex) { - logger.log(Level.INFO, "No config could be found", ex); - } + public Object intercept(InvocationContext context) throws Exception { + Object resultValue = null; // Attempt to proceed the invocation with CircuitBreaker semantics if Fault Tolerance is enabled for this method try { - String appName = faultToleranceService.getApplicationName(invocationManager, invocationContext); - // Attempt to proceed the InvocationContext with Asynchronous semantics if Fault Tolerance is enabled - if (faultToleranceService.isFaultToleranceEnabled(appName, config) - && (FaultToleranceCdiUtils.getEnabledOverrideValue( - config, CircuitBreaker.class, invocationContext) - .orElse(Boolean.TRUE))) { + if (getConfig().isEnabled(context) && getConfig().isEnabled(CircuitBreaker.class, context)) { // Only increment the invocations metric if the Retry and Bulkhead annotations aren't present - if (FaultToleranceCdiUtils.getAnnotation(beanManager, Bulkhead.class, invocationContext) == null - && FaultToleranceCdiUtils.getAnnotation(beanManager, Retry.class, invocationContext) == null) { - faultToleranceService.incrementCounterMetric(metricRegistry, - "ft." + fullMethodSignature + ".invocations.total", appName, config); + if (getConfig().getAnnotation(Bulkhead.class, context) == null + && getConfig().getAnnotation(Retry.class, context) == null) { + getMetrics().incrementInvocationsTotal(CircuitBreaker.class, context); } logger.log(Level.FINER, "Proceeding invocation with circuitbreaker semantics"); - proceededInvocationContext = circuitBreak(invocationContext); + resultValue = circuitBreak(context); } else { // If fault tolerance isn't enabled, just proceed as normal - logger.log(Level.FINE, "Fault Tolerance not enabled for {0}, proceeding normally without " - + "circuitbreaker.", faultToleranceService.getApplicationName(invocationManager, - invocationContext)); - proceededInvocationContext = invocationContext.proceed(); + logger.log(Level.FINE, "Fault Tolerance not enabled, proceeding normally without circuitbreaker."); + resultValue = context.proceed(); } } catch (Exception ex) { - Retry retry = FaultToleranceCdiUtils.getAnnotation(beanManager, Retry.class, invocationContext); + Retry retry = getConfig().getAnnotation(Retry.class, context); if (retry != null) { logger.log(Level.FINE, "Retry annotation found on method, propagating error upwards."); throw ex; } - Fallback fallback = FaultToleranceCdiUtils.getAnnotation(beanManager, Fallback.class, invocationContext); + Fallback fallback = getConfig().getAnnotation(Fallback.class, context); // Only fall back if the annotation hasn't been disabled - if (fallback != null && (FaultToleranceCdiUtils.getEnabledOverrideValue( - config, Fallback.class, invocationContext) - .orElse(Boolean.TRUE))) { + if (fallback != null && getConfig().isEnabled(Fallback.class, context)) { logger.log(Level.FINE, "Fallback annotation found on method, and no Retry annotation - " + "falling back from CircuitBreaker"); - FallbackPolicy fallbackPolicy = new FallbackPolicy(fallback, config, invocationContext); - proceededInvocationContext = fallbackPolicy.fallback(invocationContext, ex); + FallbackPolicy fallbackPolicy = new FallbackPolicy(fallback, getConfig(), getExecution(), getMetrics(), context); + resultValue = fallbackPolicy.fallback(context, ex); } else { // Increment the failure counter metric - faultToleranceService.incrementCounterMetric(metricRegistry, - "ft." + fullMethodSignature + ".invocations.failed.total", - faultToleranceService.getApplicationName(invocationManager, invocationContext), - config); - + getMetrics().incrementInvocationsFailedTotal(CircuitBreaker.class, context); throw ex; } } - return proceededInvocationContext; + return resultValue; } - private Object circuitBreak(InvocationContext invocationContext) throws Exception { - Object proceededInvocationContext = null; - - FaultToleranceService faultToleranceService = - Globals.getDefaultBaseServiceLocator().getService(FaultToleranceService.class); - CircuitBreaker circuitBreaker = FaultToleranceCdiUtils.getAnnotation(beanManager, CircuitBreaker.class, - invocationContext); + private Object circuitBreak(InvocationContext context) throws Exception { + Object resultValue = null; - MetricRegistry metricRegistry = CDI.current().select(MetricRegistry.class).get(); - String fullMethodSignature = FaultToleranceCdiUtils.getFullAnnotatedMethodSignature(invocationContext, - CircuitBreaker.class); + CircuitBreaker circuitBreaker = getConfig().getAnnotation(CircuitBreaker.class, context); - Config config = null; - try { - config = ConfigProvider.getConfig(); - } catch (IllegalArgumentException ex) { - logger.log(Level.INFO, "No config could be found", ex); - } - - String appName = faultToleranceService.getApplicationName( - Globals.getDefaultBaseServiceLocator().getService(InvocationManager.class), - invocationContext); - - Class[] failOn = circuitBreaker.failOn(); - try { - Optional optionalFailOn = FaultToleranceCdiUtils.getOverrideValue( - config, CircuitBreaker.class, "failOn", invocationContext, String.class); - if (optionalFailOn.isPresent()) { - String failOnString = optionalFailOn.get() ; - List> classList = new ArrayList<>(); - // Remove any curly or square brackets from the string, as well as any spaces and ".class"es and loop - for (String className : failOnString.replaceAll("[\\{\\[ \\]\\}]", "") - .replaceAll("\\.class", "").split(",")) { - // Get a class object - classList.add(Class.forName(className)); - } - failOn = classList.toArray(failOn); - } - } catch (NoSuchElementException nsee) { - logger.log(Level.FINER, "Could not find element in config", nsee); - } catch (ClassNotFoundException cnfe) { - logger.log(Level.INFO, "Could not find class from failOn config, defaulting to annotation. " - + "Make sure you give the full canonical class name.", cnfe); - } - - long delay = FaultToleranceCdiUtils.getOverrideValue( - config, CircuitBreaker.class, "delay", invocationContext, Long.class) - .orElse(circuitBreaker.delay()); - ChronoUnit delayUnit = FaultToleranceCdiUtils.getOverrideValue( - config, CircuitBreaker.class, "delayUnit", invocationContext, ChronoUnit.class) - .orElse(circuitBreaker.delayUnit()); - int requestVolumeThreshold = FaultToleranceCdiUtils.getOverrideValue( - config, CircuitBreaker.class, "requestVolumeThreshold", invocationContext, Integer.class) - .orElse(circuitBreaker.requestVolumeThreshold()); - double failureRatio = FaultToleranceCdiUtils.getOverrideValue( - config, CircuitBreaker.class, "failureRatio", invocationContext, Double.class) - .orElse(circuitBreaker.failureRatio()); - int successThreshold = FaultToleranceCdiUtils.getOverrideValue( - config, CircuitBreaker.class, "successThreshold", invocationContext, Integer.class) - .orElse(circuitBreaker.successThreshold()); + Class[] failOn = getConfig().failOn(circuitBreaker, context); + long delay = getConfig().delay(circuitBreaker, context); + ChronoUnit delayUnit = getConfig().delayUnit(circuitBreaker, context); + int requestVolumeThreshold = getConfig().requestVolumeThreshold(circuitBreaker, context); + double failureRatio = getConfig().failureRatio(circuitBreaker, context); + int successThreshold = getConfig().successThreshold(circuitBreaker, context); long delayMillis = Duration.of(delay, delayUnit).toMillis(); - CircuitBreakerState circuitBreakerState = faultToleranceService.getCircuitBreakerState(appName, - invocationContext.getTarget(), - invocationContext.getMethod(), circuitBreaker); - - if (faultToleranceService.areFaultToleranceMetricsEnabled(appName, config)) { - Gauge openTimeGauge = metricRegistry.getGauges() - .get("ft." + fullMethodSignature + ".circuitbreaker.open.total"); + CircuitBreakerState circuitBreakerState = getExecution().getState(requestVolumeThreshold, context); - Gauge halfOpenTimeGauge = metricRegistry.getGauges() - .get("ft." + fullMethodSignature + ".circuitbreaker.halfOpen.total"); - - Gauge closedTimeGauge = metricRegistry.getGauges() - .get("ft." + fullMethodSignature + ".circuitbreaker.closed.total"); - - registerGaugesIfNecessary(openTimeGauge, halfOpenTimeGauge, closedTimeGauge, metricRegistry, circuitBreakerState, - fullMethodSignature); + if (getConfig().isMetricsEnabled(context)) { + getMetrics().insertCircuitbreakerOpenTotal(circuitBreakerState::open, context); + getMetrics().insertCircuitbreakerHalfOpenTotal(circuitBreakerState::halfOpen, context); + getMetrics().insertCircuitbreakerClosedTotal(circuitBreakerState::closed, context); } - switch (circuitBreakerState.getCircuitState()) { case OPEN: logger.log(Level.FINER, "CircuitBreaker is Open, throwing exception"); - - faultToleranceService.incrementCounterMetric(metricRegistry, - "ft." + fullMethodSignature + ".circuitbreaker.callsPrevented.total", appName, config); + getMetrics().incrementCircuitbreakerCallsPreventedTotal(context); // If open, immediately throw an error throw new CircuitBreakerOpenException("CircuitBreaker for method " - + invocationContext.getMethod().getName() + " is in state OPEN."); + + context.getMethod().getName() + " is in state OPEN."); case CLOSED: // If closed, attempt to proceed the invocation context try { logger.log(Level.FINER, "Proceeding CircuitBreaker context"); - proceededInvocationContext = invocationContext.proceed(); + resultValue = context.proceed(); } catch (Exception ex) { logger.log(Level.FINE, "Exception executing CircuitBreaker context"); @@ -271,15 +159,12 @@ private Object circuitBreak(InvocationContext invocationContext) throws Exceptio + "recording failure against CircuitBreaker"); // Add a failure result to the queue circuitBreakerState.recordClosedResult(Boolean.FALSE); - - faultToleranceService.incrementCounterMetric(metricRegistry, - "ft." + fullMethodSignature + ".circuitbreaker.callsFailed.total", appName, config); + getMetrics().incrementCircuitbreakerCallsFailedTotal(context); // Calculate the failure ratio, and if we're over it, open the circuit breakCircuitIfRequired( Math.round(requestVolumeThreshold * failureRatio), - circuitBreakerState, delayMillis, metricRegistry, fullMethodSignature, - faultToleranceService, appName, config); + circuitBreakerState, delayMillis, context); } // Finally, propagate the error upwards @@ -288,20 +173,18 @@ private Object circuitBreak(InvocationContext invocationContext) throws Exceptio // If everything is bon, add a success value circuitBreakerState.recordClosedResult(Boolean.TRUE); - faultToleranceService.incrementCounterMetric(metricRegistry, - "ft." + fullMethodSignature + ".circuitbreaker.callsSucceeded.total", appName, config); + getMetrics().incrementCircuitbreakerCallsSucceededTotal(context); // Calculate the failure ratio, and if we're over it, open the circuit breakCircuitIfRequired( Math.round(requestVolumeThreshold * failureRatio), - circuitBreakerState, delayMillis, metricRegistry, fullMethodSignature, - faultToleranceService, appName, config); + circuitBreakerState, delayMillis, context); break; case HALF_OPEN: // If half-open, attempt to proceed the invocation context try { logger.log(Level.FINER, "Proceeding half open CircuitBreaker context"); - proceededInvocationContext = invocationContext.proceed(); + resultValue = context.proceed(); } catch (Exception ex) { logger.log(Level.FINE, "Exception executing CircuitBreaker context"); @@ -310,13 +193,12 @@ private Object circuitBreak(InvocationContext invocationContext) throws Exceptio logger.log(Level.FINE, "Caught exception is included in CircuitBreaker failOn, " + "reopening half open circuit"); - faultToleranceService.incrementCounterMetric(metricRegistry, - "ft." + fullMethodSignature + ".circuitbreaker.callsFailed.total", appName, config); + getMetrics().incrementCircuitbreakerCallsFailedTotal(context); // Open the circuit again, and reset the half-open result counter circuitBreakerState.setCircuitState(CircuitBreakerState.CircuitState.OPEN); circuitBreakerState.resetHalfOpenSuccessfulResultCounter(); - scheduleHalfOpen(delayMillis, circuitBreakerState); + getExecution().scheduleHalfOpen(delayMillis, circuitBreakerState); } throw ex; @@ -324,8 +206,7 @@ private Object circuitBreak(InvocationContext invocationContext) throws Exceptio // If the invocation context hasn't thrown an error, record a success circuitBreakerState.incrementHalfOpenSuccessfulResultCounter(); - faultToleranceService.incrementCounterMetric(metricRegistry, - "ft." + fullMethodSignature + ".circuitbreaker.callsSucceeded.total", appName, config); + getMetrics().incrementCircuitbreakerCallsSucceededTotal(context); logger.log(Level.FINER, "Number of consecutive successful circuitbreaker executions = {0}", circuitBreakerState.getHalfOpenSuccessFulResultCounter()); @@ -347,28 +228,7 @@ private Object circuitBreak(InvocationContext invocationContext) throws Exceptio break; } - return proceededInvocationContext; - } - - /** - * Helper method that schedules the CircuitBreaker state to be set to HalfOpen after the configured delay - * @param delayMillis The number of milliseconds to wait before setting the state - * @param circuitBreakerState The CircuitBreakerState to set the state of - * @throws NamingException If the ManagedScheduledExecutor couldn't be found - */ - private static void scheduleHalfOpen(long delayMillis, CircuitBreakerState circuitBreakerState) throws NamingException { - Runnable halfOpen = () -> { - circuitBreakerState.setCircuitState(CircuitBreakerState.CircuitState.HALF_OPEN); - logger.log(Level.FINE, "Setting CircuitBreaker state to half open"); - }; - - FaultToleranceService faultToleranceService = Globals.getDefaultBaseServiceLocator() - .getService(FaultToleranceService.class); - ManagedScheduledExecutorService managedScheduledExecutorService = faultToleranceService. - getManagedScheduledExecutorService(); - - managedScheduledExecutorService.schedule(halfOpen, delayMillis, TimeUnit.MILLISECONDS); - logger.log(Level.FINER, "CircuitBreaker half open state scheduled in {0} milliseconds", delayMillis); + return resultValue; } /** @@ -377,7 +237,7 @@ private static void scheduleHalfOpen(long delayMillis, CircuitBreakerState circu * @param ex The exception to check * @return True if the exception is included in the array */ - private static boolean shouldFail(Class[] failOn, Exception ex) { + private boolean shouldFail(Class[] failOn, Exception ex) { boolean shouldFail = false; if (failOn[0] != Throwable.class) { @@ -407,9 +267,8 @@ private static boolean shouldFail(Class[] failOn, Exception return shouldFail; } - private static void breakCircuitIfRequired(long failureThreshold, CircuitBreakerState circuitBreakerState, - long delayMillis, MetricRegistry metricRegistry, String fullMethodSignature, - FaultToleranceService faultToleranceService, String appName, Config config) throws NamingException { + private void breakCircuitIfRequired(long failureThreshold, CircuitBreakerState circuitBreakerState, + long delayMillis, InvocationContext context) throws Exception { // If we're over the failure threshold, open the circuit if (circuitBreakerState.isOverFailureThreshold(failureThreshold)) { logger.log(Level.FINE, "CircuitBreaker is over failure threshold {0}, opening circuit", @@ -419,36 +278,11 @@ private static void breakCircuitIfRequired(long failureThreshold, CircuitBreaker circuitBreakerState.setCircuitState(CircuitBreakerState.CircuitState.OPEN); // Update the opened metric counter - faultToleranceService.incrementCounterMetric(metricRegistry, - "ft." + fullMethodSignature + ".circuitbreaker.opened.total", appName, config); + getMetrics().incrementCircuitbreakerOpenedTotal(context); // Kick off a thread that will half-open the circuit after the specified delay - scheduleHalfOpen(delayMillis, circuitBreakerState); + getExecution().scheduleHalfOpen(delayMillis, circuitBreakerState); } } - private static void registerGaugesIfNecessary(Gauge openTimeGauge, Gauge halfOpenTimeGauge, Gauge closedTimeGauge, - MetricRegistry metricRegistry, CircuitBreakerState circuitBreakerState, String fullMethodSignature) { - - // Register a open time gauge if there isn't one - if (openTimeGauge == null) { - openTimeGauge = () -> circuitBreakerState.updateAndGet(CircuitBreakerState.CircuitState.OPEN); - - metricRegistry.register("ft." + fullMethodSignature + ".circuitbreaker.open.total", openTimeGauge); - } - - // Register a open time gauge if there isn't one - if (halfOpenTimeGauge == null) { - halfOpenTimeGauge = () -> circuitBreakerState.updateAndGet(CircuitBreakerState.CircuitState.HALF_OPEN); - - metricRegistry.register("ft." + fullMethodSignature + ".circuitbreaker.halfOpen.total", halfOpenTimeGauge); - } - - // Register a open time gauge if there isn't one - if (closedTimeGauge == null) { - closedTimeGauge = () -> circuitBreakerState.updateAndGet(CircuitBreakerState.CircuitState.CLOSED); - - metricRegistry.register("ft." + fullMethodSignature + ".circuitbreaker.closed.total", closedTimeGauge); - } - } } diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/FaultToleranceInterceptor.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/FaultToleranceInterceptor.java new file mode 100644 index 00000000000..0f7b4fde333 --- /dev/null +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/FaultToleranceInterceptor.java @@ -0,0 +1,59 @@ +package fish.payara.microprofile.faulttolerance.interceptors; + +import java.lang.annotation.Annotation; +import java.util.Set; + +import javax.annotation.Priority; +import javax.enterprise.inject.spi.BeanManager; +import javax.inject.Inject; +import javax.interceptor.AroundInvoke; +import javax.interceptor.Interceptor; +import javax.interceptor.InvocationContext; + +import org.glassfish.internal.api.Globals; + +import fish.payara.microprofile.faulttolerance.FaultToleranceConfig; +import fish.payara.microprofile.faulttolerance.FaultToleranceExecution; +import fish.payara.microprofile.faulttolerance.cdi.CdiFaultToleranceConfig; +import fish.payara.microprofile.faulttolerance.cdi.FaultToleranceCdiUtils.Stereotypes; +import fish.payara.microprofile.faulttolerance.model.FaultToleranceBehaviour; +import fish.payara.microprofile.faulttolerance.model.FaultTolerancePolicy; + +@Interceptor +@FaultToleranceBehaviour +@Priority(Interceptor.Priority.PLATFORM_AFTER) +public class FaultToleranceInterceptor implements Stereotypes { + + @Inject + private BeanManager beanManager; + + @AroundInvoke + public Object intercept(InvocationContext context) throws Exception { + try { + FaultToleranceExecution execution = + Globals.getDefaultBaseServiceLocator().getService(FaultToleranceExecution.class); + FaultTolerancePolicy info = FaultTolerancePolicy.get(context, this::getConfig); + if (info.isFaultToleranceEnabled) { + System.out.println("yes"); + } + } catch (Exception e) { + e.printStackTrace(); + } + return context.proceed(); + } + + private FaultToleranceConfig getConfig() { + return new CdiFaultToleranceConfig(null, this); + } + + @Override + public boolean isStereotype(Class annotationType) { + return beanManager.isStereotype(annotationType); + } + + @Override + public Set getStereotypeDefinition(Class stereotype) { + return beanManager.getStereotypeDefinition(stereotype); + } +} + diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/RetryInterceptor.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/RetryInterceptor.java index 583abd1c6d5..ac062509944 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/RetryInterceptor.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/RetryInterceptor.java @@ -39,34 +39,18 @@ */ package fish.payara.microprofile.faulttolerance.interceptors; -import fish.payara.microprofile.faulttolerance.FaultToleranceService; -import fish.payara.microprofile.faulttolerance.cdi.FaultToleranceCdiUtils; import fish.payara.microprofile.faulttolerance.interceptors.fallback.FallbackPolicy; import java.time.Duration; import java.time.temporal.ChronoUnit; -import java.util.ArrayList; -import java.util.List; -import java.util.NoSuchElementException; -import java.util.Optional; import java.util.concurrent.ThreadLocalRandom; import java.util.logging.Level; -import java.util.logging.Logger; import javax.annotation.Priority; -import javax.enterprise.inject.spi.BeanManager; -import javax.inject.Inject; import javax.interceptor.AroundInvoke; import javax.interceptor.Interceptor; import javax.interceptor.InvocationContext; -import fish.payara.notification.requesttracing.RequestTraceSpan; -import javax.enterprise.inject.spi.CDI; -import org.eclipse.microprofile.config.Config; -import org.eclipse.microprofile.config.ConfigProvider; import org.eclipse.microprofile.faulttolerance.Fallback; import org.eclipse.microprofile.faulttolerance.Retry; -import org.eclipse.microprofile.metrics.MetricRegistry; -import org.glassfish.api.invocation.InvocationManager; -import org.glassfish.internal.api.Globals; /** * @@ -75,188 +59,78 @@ @Interceptor @Retry @Priority(Interceptor.Priority.PLATFORM_AFTER + 5) -public class RetryInterceptor { +public class RetryInterceptor extends BaseFaultToleranceInterceptor { - private static final Logger logger = Logger.getLogger(RetryInterceptor.class.getName()); - - @Inject - private BeanManager beanManager; + public RetryInterceptor() { + super(Retry.class, false); + } @AroundInvoke - public Object intercept(InvocationContext invocationContext) throws Exception { - Object proceededInvocationContext = null; - - FaultToleranceService faultToleranceService = - Globals.getDefaultBaseServiceLocator().getService(FaultToleranceService.class); - InvocationManager invocationManager = Globals.getDefaultBaseServiceLocator() - .getService(InvocationManager.class); - - MetricRegistry metricRegistry = CDI.current().select(MetricRegistry.class).get(); - String fullMethodSignature = FaultToleranceCdiUtils.getFullAnnotatedMethodSignature(invocationContext, - Retry.class); + public Object intercept(InvocationContext context) throws Exception { + Object resultValue = null; - Config config = null; try { - config = ConfigProvider.getConfig(); - } catch (IllegalArgumentException iae) { - logger.log(Level.INFO, "No config could be found", iae); - } - - try { - String appName = faultToleranceService.getApplicationName(invocationManager, invocationContext); - // Attempt to proceed the InvocationContext with Asynchronous semantics if Fault Tolerance is enabled for this // method - if (faultToleranceService.isFaultToleranceEnabled(appName, config) - && (FaultToleranceCdiUtils.getEnabledOverrideValue( - config, Retry.class, invocationContext) - .orElse(Boolean.TRUE))) { + if (getConfig().isEnabled(context) && getConfig().isEnabled(Retry.class, context)) { // Increment the invocations metric - faultToleranceService.incrementCounterMetric(metricRegistry, - "ft." + fullMethodSignature + ".invocations.total", appName, config); + getMetrics().incrementInvocationsTotal(Retry.class, context); logger.log(Level.FINER, "Proceeding invocation with retry semantics"); - proceededInvocationContext = retry(invocationContext); + resultValue = retry(context); } else { // If fault tolerance isn't enabled, just proceed as normal - logger.log(Level.FINE, "Fault Tolerance not enabled for {0}, proceeding normally without retry.", - faultToleranceService.getApplicationName(invocationManager, invocationContext)); - proceededInvocationContext = invocationContext.proceed(); + logger.log(Level.FINE, "Fault Tolerance not enabled, proceeding normally without retry."); + resultValue = context.proceed(); } } catch (Exception ex) { - Fallback fallback = FaultToleranceCdiUtils.getAnnotation(beanManager, Fallback.class, invocationContext); + Fallback fallback = getConfig().getAnnotation(Fallback.class, context); // Only fall back if the annotation hasn't been disabled - if (fallback != null && (FaultToleranceCdiUtils.getEnabledOverrideValue( - config, Fallback.class, invocationContext) - .orElse(Boolean.TRUE))) { + if (fallback != null && getConfig().isEnabled(Fallback.class, context)) { logger.log(Level.FINE, "Fallback annotation found on method - falling back from Retry"); - FallbackPolicy fallbackPolicy = new FallbackPolicy(fallback, config, invocationContext); - proceededInvocationContext = fallbackPolicy.fallback(invocationContext, ex); + FallbackPolicy fallbackPolicy = new FallbackPolicy(fallback, getConfig(), getExecution(), getMetrics(), context); + resultValue = fallbackPolicy.fallback(context, ex); } else { // Increment the failure counter metric - faultToleranceService.incrementCounterMetric(metricRegistry, - "ft." + fullMethodSignature + ".invocations.failed.total", - faultToleranceService.getApplicationName(invocationManager, invocationContext), - config); - + getMetrics().incrementInvocationsFailedTotal(Retry.class, context); throw ex; } } - return proceededInvocationContext; + return resultValue; } /** * Proceeds the given invocation context with Retry semantics. - * @param invocationContext The invocation context to proceed + * @param context The invocation context to proceed * @return The proceeded invocation context * @throws Exception If the invocation throws an exception that shouldn't be retried, or if all retry attempts are * expended */ - private Object retry(InvocationContext invocationContext) throws Exception { - Object proceededInvocationContext = null; - Retry retry = FaultToleranceCdiUtils.getAnnotation(beanManager, Retry.class, invocationContext); - - FaultToleranceService faultToleranceService = - Globals.getDefaultBaseServiceLocator().getService(FaultToleranceService.class); - InvocationManager invocationManager = Globals.getDefaultBaseServiceLocator() - .getService(InvocationManager.class); + private Object retry(InvocationContext context) throws Exception { + Object resultValue = null; + Retry retry = getConfig().getAnnotation(Retry.class, context); - MetricRegistry metricRegistry = CDI.current().select(MetricRegistry.class).get(); - String fullMethodSignature = FaultToleranceCdiUtils.getFullAnnotatedMethodSignature(invocationContext, - Retry.class); - String appName = faultToleranceService.getApplicationName(invocationManager, invocationContext); - - Config config = null; try { - config = ConfigProvider.getConfig(); - } catch (IllegalArgumentException iae) { - logger.log(Level.INFO, "No config could be found", iae); - } - - try { - proceededInvocationContext = invocationContext.proceed(); - faultToleranceService.incrementCounterMetric(metricRegistry, - "ft." + fullMethodSignature + ".retry.callsSucceededNotRetried.total", appName, config); + resultValue = context.proceed(); + getMetrics().incrementRetryCallsSucceededNotRetriedTotal(context); } catch (Exception ex) { - Class[] retryOn = retry.retryOn(); - try { - Optional optionalRetryOn = FaultToleranceCdiUtils.getOverrideValue( - config, Retry.class, "retryOn", invocationContext, String.class); - if (optionalRetryOn.isPresent()) { - String retryOnString = optionalRetryOn.get(); - - List> classList = new ArrayList<>(); - - // Remove any curly or square brackets from the string, as well as any spaces and ".class"es - for (String className : retryOnString.replaceAll("[\\{\\[ \\]\\}]", "") - .replaceAll("\\.class", "").split(",")) { - classList.add(Class.forName(className)); - } - - retryOn = classList.toArray(retryOn); - } - } catch (NoSuchElementException nsee) { - logger.log(Level.FINER, "Could not find element in config", nsee); - } catch (ClassNotFoundException cnfe) { - logger.log(Level.INFO, "Could not find class from retryOn config, defaulting to annotation. " - + "Make sure you give the full canonical class name.", cnfe); - } - - Class[] abortOn = retry.abortOn(); - try { - Optional optionalAbortOn = FaultToleranceCdiUtils.getOverrideValue( - config, Retry.class, "abortOn", invocationContext, String.class); - if (optionalAbortOn.isPresent()) { - String abortOnString = optionalAbortOn.get(); - - List> classList = new ArrayList<>(); - - // Remove any curly or square brackets from the string, as well as any spaces and ".class"es - for (String className : abortOnString.replaceAll("[\\{\\[ \\]\\}]", "") - .replaceAll("\\.class", "").split(",")) { - classList.add(Class.forName(className)); - } - - abortOn = classList.toArray(abortOn); - } - } catch (NoSuchElementException nsee) { - logger.log(Level.FINER, "Could not find element in config", nsee); - } catch (ClassNotFoundException cnfe) { - logger.log(Level.INFO, "Could not find class from abortOn config, defaulting to annotation. " - + "Make sure you give the full canonical class name.", cnfe); - } + final Class[] retryOn = getConfig().retryOn(retry, context); + final Class[] abortOn = getConfig().abortOn(retry, context); if (!shouldRetry(retryOn, abortOn, ex)) { logger.log(Level.FINE, "Exception is contained in retryOn or abortOn, not retrying.", ex); throw ex; } - int maxRetries = FaultToleranceCdiUtils.getOverrideValue( - config, Retry.class, "maxRetries", invocationContext, Integer.class) - .orElse(retry.maxRetries()); - long delay = FaultToleranceCdiUtils.getOverrideValue( - config, Retry.class, "delay", invocationContext, Long.class) - .orElse(retry.delay()); - // Look for a String and cast to ChronoUnit - Use the Common Sense Convertor - ChronoUnit delayUnit = FaultToleranceCdiUtils.getOverrideValue( - config, Retry.class, "delayUnit", invocationContext, ChronoUnit.class) - .orElse(retry.delayUnit()); - long maxDuration = FaultToleranceCdiUtils.getOverrideValue( - config, Retry.class, "maxDuration", invocationContext, Long.class) - .orElse(retry.maxDuration()); - // Look for a String and cast to ChronoUnit - Use the Common Sense Convertor - ChronoUnit durationUnit = FaultToleranceCdiUtils.getOverrideValue( - config, Retry.class, "durationUnit", invocationContext, ChronoUnit.class) - .orElse(retry.durationUnit()); - long jitter = FaultToleranceCdiUtils.getOverrideValue( - config, Retry.class, "jitter", invocationContext, Long.class) - .orElse(retry.jitter()); - // Look for a String and cast to ChronoUnit - Use the Common Sense Convertor - ChronoUnit jitterDelayUnit = FaultToleranceCdiUtils.getOverrideValue( - config, Retry.class, "jitterDelayUnit", invocationContext, ChronoUnit.class) - .orElse(retry.jitterDelayUnit()); + int maxRetries = getConfig().maxRetries(retry, context); + long delay = getConfig().delay(retry, context); + ChronoUnit delayUnit = getConfig().delayUnit(retry, context); + long maxDuration = getConfig().maxDuration(retry, context); + ChronoUnit durationUnit = getConfig().durationUnit(retry, context); + long jitter = getConfig().jitter(retry, context); + ChronoUnit jitterDelayUnit = getConfig().jitterDelayUnit(retry, context); long delayMillis = Duration.of(delay, delayUnit).toMillis(); long jitterMillis = Duration.of(jitter, jitterDelayUnit).toMillis(); @@ -264,8 +138,7 @@ private Object retry(InvocationContext invocationContext) throws Exception { Exception retryException = ex; - faultToleranceService.startFaultToleranceSpan(new RequestTraceSpan("retryMethod"), invocationManager, - invocationContext); + getExecution().startTrace("retryMethod", context); boolean succeeded = false; @@ -274,13 +147,11 @@ private Object retry(InvocationContext invocationContext) throws Exception { logger.log(Level.FINER, "Retrying until maxDuration is breached."); while (System.currentTimeMillis() < timeoutTime) { - faultToleranceService.incrementCounterMetric(metricRegistry, - "ft." + fullMethodSignature + ".retry.retries.total", appName, config); + getMetrics().incrementRetryRetriesTotal(context); try { - proceededInvocationContext = invocationContext.proceed(); + resultValue = context.proceed(); succeeded = true; - faultToleranceService.incrementCounterMetric(metricRegistry, - "ft." + fullMethodSignature + ".retry.callsSucceededRetried.total", appName, config); + getMetrics().incrementRetryCallsSucceededRetriedTotal(context); break; } catch (Exception caughtException) { retryException = caughtException; @@ -289,12 +160,11 @@ private Object retry(InvocationContext invocationContext) throws Exception { } if (delayMillis > 0 || jitterMillis > 0) { - faultToleranceService.startFaultToleranceSpan(new RequestTraceSpan("delayRetry"), - invocationManager, invocationContext); + getExecution().startTrace("delayRetry", context); try { Thread.sleep(delayMillis + ThreadLocalRandom.current().nextLong(0, jitterMillis)); } finally { - faultToleranceService.endFaultToleranceSpan(); + getExecution().endTrace(); } } } @@ -302,12 +172,10 @@ private Object retry(InvocationContext invocationContext) throws Exception { } else if (maxRetries == -1 && maxDuration == 0) { logger.log(Level.INFO, "Retrying potentially forever!"); while (true) { - faultToleranceService.incrementCounterMetric(metricRegistry, - "ft." + fullMethodSignature + ".retry.retries.total", appName, config); + getMetrics().incrementRetryRetriesTotal(context); try { - proceededInvocationContext = invocationContext.proceed(); - faultToleranceService.incrementCounterMetric(metricRegistry, - "ft." + fullMethodSignature + ".retry.callsSucceededRetried.total", appName, config); + resultValue = context.proceed(); + getMetrics().incrementRetryCallsSucceededRetriedTotal(context); succeeded = true; break; } catch (Exception caughtException) { @@ -317,12 +185,11 @@ private Object retry(InvocationContext invocationContext) throws Exception { } if (delayMillis > 0 || jitterMillis > 0) { - faultToleranceService.startFaultToleranceSpan(new RequestTraceSpan("delayRetry"), - invocationManager, invocationContext); + getExecution().startTrace("delayRetry", context); try { Thread.sleep(delayMillis + ThreadLocalRandom.current().nextLong(0, jitterMillis)); } finally { - faultToleranceService.endFaultToleranceSpan(); + getExecution().endTrace(); } } } @@ -332,12 +199,10 @@ private Object retry(InvocationContext invocationContext) throws Exception { "Retrying as long as maxDuration ({0}ms) isn''t breached, and no more than {1} times", new Object[]{Duration.of(maxDuration, durationUnit).toMillis(), maxRetries}); while (maxRetries > 0 && System.currentTimeMillis() < timeoutTime) { - faultToleranceService.incrementCounterMetric(metricRegistry, - "ft." + fullMethodSignature + ".retry.retries.total", appName, config); + getMetrics().incrementRetryRetriesTotal(context); try { - proceededInvocationContext = invocationContext.proceed(); - faultToleranceService.incrementCounterMetric(metricRegistry, - "ft." + fullMethodSignature + ".retry.callsSucceededRetried.total", appName, config); + resultValue = context.proceed(); + getMetrics().incrementRetryCallsSucceededRetriedTotal(context); succeeded = true; break; } catch (Exception caughtException) { @@ -347,12 +212,11 @@ private Object retry(InvocationContext invocationContext) throws Exception { } if (delayMillis > 0 || jitterMillis > 0) { - faultToleranceService.startFaultToleranceSpan(new RequestTraceSpan("delayRetry"), - invocationManager, invocationContext); + getExecution().startTrace("delayRetry", context); try { Thread.sleep(delayMillis + ThreadLocalRandom.current().nextLong(0, jitterMillis)); } finally { - faultToleranceService.endFaultToleranceSpan(); + getExecution().endTrace(); } } @@ -362,12 +226,10 @@ private Object retry(InvocationContext invocationContext) throws Exception { } else { logger.log(Level.INFO, "Retrying no more than {0} times", maxRetries); while (maxRetries > 0) { - faultToleranceService.incrementCounterMetric(metricRegistry, - "ft." + fullMethodSignature + ".retry.retries.total", appName, config); + getMetrics().incrementRetryRetriesTotal(context); try { - proceededInvocationContext = invocationContext.proceed(); - faultToleranceService.incrementCounterMetric(metricRegistry, - "ft." + fullMethodSignature + ".retry.callsSucceededRetried.total", appName, config); + resultValue = context.proceed(); + getMetrics().incrementRetryCallsSucceededRetriedTotal(context); succeeded = true; break; } catch (Exception caughtException) { @@ -377,12 +239,11 @@ private Object retry(InvocationContext invocationContext) throws Exception { } if (delayMillis > 0 || jitterMillis > 0) { - faultToleranceService.startFaultToleranceSpan(new RequestTraceSpan("delayRetry"), - invocationManager, invocationContext); + getExecution().startTrace("delayRetry", context); try { Thread.sleep(delayMillis + ThreadLocalRandom.current().nextLong(0, jitterMillis)); } finally { - faultToleranceService.endFaultToleranceSpan(); + getExecution().endTrace(); } } @@ -391,17 +252,16 @@ private Object retry(InvocationContext invocationContext) throws Exception { } } } finally { - faultToleranceService.endFaultToleranceSpan(); + getExecution().endTrace(); } if (!succeeded) { - faultToleranceService.incrementCounterMetric(metricRegistry, - "ft." + fullMethodSignature + ".retry.callsFailed.total", appName, config); + getMetrics().incrementRetryCallsFailedTotal(context); throw retryException; } } - return proceededInvocationContext; + return resultValue; } /** @@ -411,7 +271,7 @@ private Object retry(InvocationContext invocationContext) throws Exception { * @param ex The caught exception * @return True if retry should be attempted. */ - private static boolean shouldRetry(Class[] retryOn, Class[] abortOn, + private boolean shouldRetry(Class[] retryOn, Class[] abortOn, Exception ex) { boolean shouldRetry = false; diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/TimeoutInterceptor.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/TimeoutInterceptor.java index e2204da71c5..f0012967cd5 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/TimeoutInterceptor.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/TimeoutInterceptor.java @@ -39,37 +39,22 @@ */ package fish.payara.microprofile.faulttolerance.interceptors; -import fish.payara.microprofile.faulttolerance.FaultToleranceService; -import fish.payara.microprofile.faulttolerance.cdi.FaultToleranceCdiUtils; import fish.payara.microprofile.faulttolerance.interceptors.fallback.FallbackPolicy; import java.time.Duration; import java.time.temporal.ChronoUnit; import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; import java.util.logging.Level; -import java.util.logging.Logger; import javax.annotation.Priority; -import javax.enterprise.concurrent.ManagedScheduledExecutorService; -import javax.enterprise.inject.spi.BeanManager; -import javax.enterprise.inject.spi.CDI; -import javax.inject.Inject; import javax.interceptor.AroundInvoke; import javax.interceptor.Interceptor; import javax.interceptor.InvocationContext; -import javax.naming.NamingException; -import org.eclipse.microprofile.config.Config; -import org.eclipse.microprofile.config.ConfigProvider; import org.eclipse.microprofile.faulttolerance.Bulkhead; import org.eclipse.microprofile.faulttolerance.CircuitBreaker; import org.eclipse.microprofile.faulttolerance.Fallback; import org.eclipse.microprofile.faulttolerance.Retry; import org.eclipse.microprofile.faulttolerance.Timeout; import org.eclipse.microprofile.faulttolerance.exceptions.TimeoutException; -import org.eclipse.microprofile.metrics.MetricRegistry; -import org.glassfish.api.invocation.InvocationManager; -import org.glassfish.hk2.api.ServiceLocator; -import org.glassfish.internal.api.Globals; /** * @@ -78,211 +63,120 @@ @Interceptor @Timeout @Priority(Interceptor.Priority.PLATFORM_AFTER + 20) -public class TimeoutInterceptor { - - private static final Logger logger = Logger.getLogger(TimeoutInterceptor.class.getName()); - - @Inject - private BeanManager beanManager; +public class TimeoutInterceptor extends BaseFaultToleranceInterceptor { + + public TimeoutInterceptor() { + super(Timeout.class, true); + } @AroundInvoke - public Object intercept(InvocationContext invocationContext) throws Exception { - Object proceededInvocationContext = null; - - ServiceLocator serviceLocator = Globals.getDefaultBaseServiceLocator(); - FaultToleranceService faultToleranceService = serviceLocator.getService(FaultToleranceService.class); - InvocationManager invocationManager = serviceLocator.getService(InvocationManager.class); - - MetricRegistry metricRegistry = CDI.current().select(MetricRegistry.class).get(); - String fullMethodSignature = FaultToleranceCdiUtils.getFullAnnotatedMethodSignature(invocationContext, - Timeout.class); - - Config config = null; - try { - config = ConfigProvider.getConfig(); - } catch (IllegalArgumentException ex) { - logger.log(Level.INFO, "No config could be found", ex); - } - + public Object intercept(InvocationContext context) throws Exception { + Object resultValue = null; + try { - String appName = faultToleranceService.getApplicationName(invocationManager, invocationContext); - // Attempt to proceed the InvocationContext with Asynchronous semantics if Fault Tolerance is enabled for this // method - if (faultToleranceService.isFaultToleranceEnabled(appName, config) - && (FaultToleranceCdiUtils.getEnabledOverrideValue( - config, Timeout.class, invocationContext) - .orElse(Boolean.TRUE))) { + if (getConfig().isEnabled(context) && getConfig().isEnabled(Timeout.class, context)) { // Only increment the invocations metric if the Retry, Bulkhead, and CircuitBreaker annotations aren't present - if (FaultToleranceCdiUtils.getAnnotation(beanManager, Bulkhead.class, invocationContext) == null - && FaultToleranceCdiUtils.getAnnotation(beanManager, Retry.class, invocationContext) == null - && FaultToleranceCdiUtils.getAnnotation( - beanManager, CircuitBreaker.class, invocationContext) == null) { - faultToleranceService.incrementCounterMetric(metricRegistry, - "ft." + fullMethodSignature + ".invocations.total", appName, config); + if (getConfig().getAnnotation(Bulkhead.class, context) == null + && getConfig().getAnnotation(Retry.class, context) == null + && getConfig().getAnnotation(CircuitBreaker.class, context) == null) { + getMetrics().incrementInvocationsTotal(Timeout.class, context); } - + logger.log(Level.FINER, "Proceeding invocation with timeout semantics"); - proceededInvocationContext = timeout(invocationContext); + resultValue = timeout(context); } else { // If fault tolerance isn't enabled, just proceed as normal - logger.log(Level.FINE, "Fault Tolerance not enabled for {0}, proceeding normally without timeout.", - faultToleranceService.getApplicationName(invocationManager, invocationContext)); - proceededInvocationContext = invocationContext.proceed(); + logger.log(Level.FINE, "Fault Tolerance not enabled, proceeding normally without timeout."); + resultValue = context.proceed(); } } catch (Exception ex) { - Retry retry = FaultToleranceCdiUtils.getAnnotation(beanManager, Retry.class, invocationContext); - + Retry retry = getConfig().getAnnotation(Retry.class, context); + if (retry != null) { logger.log(Level.FINE, "Retry annotation found on method, propagating error upwards."); throw ex; } - Fallback fallback = FaultToleranceCdiUtils.getAnnotation(beanManager, Fallback.class, - invocationContext); + Fallback fallback = getConfig().getAnnotation(Fallback.class, context); // Only fall back if the annotation hasn't been disabled - if (fallback != null && (FaultToleranceCdiUtils.getEnabledOverrideValue( - config, Fallback.class, invocationContext) - .orElse(Boolean.TRUE))) { + if (fallback != null && getConfig().isEnabled(Fallback.class, context)) { logger.log(Level.FINE, "Fallback annotation found on method, and no Retry annotation - " + "falling back from Timeout"); - FallbackPolicy fallbackPolicy = new FallbackPolicy(fallback, config, invocationContext); - proceededInvocationContext = fallbackPolicy.fallback(invocationContext, ex); + FallbackPolicy fallbackPolicy = new FallbackPolicy(fallback, getConfig(), getExecution(), getMetrics(), context); + resultValue = fallbackPolicy.fallback(context, ex); } else { // Increment the failure counter metric - faultToleranceService.incrementCounterMetric(metricRegistry, - "ft." + fullMethodSignature + ".invocations.failed.total", - faultToleranceService.getApplicationName(invocationManager, invocationContext), - config); - + getMetrics().incrementInvocationsFailedTotal(Timeout.class, context); throw ex; } } - - return proceededInvocationContext; + return resultValue; } /** * Proceeds the given invocation context with Timeout semantics. - * @param invocationContext The invocation context to proceed. + * @param context The invocation context to proceed. * @return The result of the invocation context. * @throws Exception If the invocation context execution throws an exception */ - private Object timeout(InvocationContext invocationContext) throws Exception { - Object proceededInvocationContext = null; - - FaultToleranceService faultToleranceService = - Globals.getDefaultBaseServiceLocator().getService(FaultToleranceService.class); - Timeout timeout = FaultToleranceCdiUtils.getAnnotation(beanManager, Timeout.class, invocationContext); - - MetricRegistry metricRegistry = CDI.current().select(MetricRegistry.class).get(); - String fullMethodSignature = FaultToleranceCdiUtils.getFullAnnotatedMethodSignature(invocationContext, - Timeout.class); - String appName = faultToleranceService.getApplicationName( - Globals.getDefaultBaseServiceLocator().getService(InvocationManager.class), - invocationContext); - - Config config = null; - try { - config = ConfigProvider.getConfig(); - } catch (IllegalArgumentException ex) { - logger.log(Level.INFO, "No config could be found", ex); - } - - long value = FaultToleranceCdiUtils.getOverrideValue( - config, Timeout.class, "value", invocationContext, Long.class) - .orElse(timeout.value()); - // Look for a String and cast to ChronoUnit - Use the Common Sense Convertor - ChronoUnit unit = FaultToleranceCdiUtils.getOverrideValue( - config, Timeout.class, "unit", invocationContext, ChronoUnit.class) - .orElse(timeout.unit()); + private Object timeout(InvocationContext context) throws Exception { + Object resultValue = null; + + Timeout timeout = getConfig().getAnnotation(Timeout.class, context); + + long value = getConfig().value(timeout, context); + ChronoUnit unit = getConfig().unit(timeout, context); Future timeoutFuture = null; long timeoutMillis = Duration.of(value, unit).toMillis(); long timeoutTime = System.currentTimeMillis() + timeoutMillis; long executionStartTime = System.nanoTime(); - + try { - timeoutFuture = startTimeout(timeoutMillis); - proceededInvocationContext = invocationContext.proceed(); + timeoutFuture = getExecution().timeoutIn(timeoutMillis); + resultValue = context.proceed(); stopTimeout(timeoutFuture); if (System.currentTimeMillis() > timeoutTime) { // Record the timeout for MP Metrics - faultToleranceService.incrementCounterMetric(metricRegistry, - "ft." + fullMethodSignature + ".timeout.callsTimedOut.total", appName, config); - + getMetrics().incrementTimeoutCallsTimedOutTotal(context); logger.log(Level.FINE, "Execution timed out"); throw new TimeoutException(); } - + // Record the execution time for MP Metrics - faultToleranceService.updateHistogramMetric(metricRegistry, - "ft." + fullMethodSignature + ".timeout.executionDuration", - System.nanoTime() - executionStartTime, - appName, config); + getMetrics().addTimeoutExecutionDuration(System.nanoTime() - executionStartTime, context); // Record the successfuly completion for MP Metrics - faultToleranceService.incrementCounterMetric(metricRegistry, - "ft." + fullMethodSignature + ".timeout.callsNotTimedOut.total", appName, config); + getMetrics().incrementTimeoutCallsNotTimedOutTotal(context); } catch (InterruptedException ie) { // Record the execution time for MP Metrics - faultToleranceService.updateHistogramMetric(metricRegistry, - "ft." + fullMethodSignature + ".timeout.executionDuration", - System.nanoTime() - executionStartTime, - appName, config); - + getMetrics().addTimeoutExecutionDuration(System.nanoTime() - executionStartTime, context); + if (System.currentTimeMillis() > timeoutTime) { // Record the timeout for MP Metrics - faultToleranceService.incrementCounterMetric(metricRegistry, - "ft." + fullMethodSignature + ".timeout.callsTimedOut.total", appName, config); - + getMetrics().incrementTimeoutCallsTimedOutTotal(context); logger.log(Level.FINE, "Execution timed out"); throw new TimeoutException(ie); } } catch (Exception ex) { // Record the execution time for MP Metrics - faultToleranceService.updateHistogramMetric(metricRegistry, - "ft." + fullMethodSignature + ".timeout.executionDuration", - System.nanoTime() - executionStartTime, - appName, config); - + getMetrics().addTimeoutExecutionDuration(System.nanoTime() - executionStartTime, context); + // Deal with cases where someone has caught the thread.interrupt() and thrown the exception as something else if (ex.getCause() instanceof InterruptedException && System.currentTimeMillis() > timeoutTime) { // Record the timeout for MP Metrics - faultToleranceService.incrementCounterMetric(metricRegistry, - "ft." + fullMethodSignature + ".timeout.callsTimedOut.total", appName, config); - + getMetrics().incrementTimeoutCallsTimedOutTotal(context); logger.log(Level.FINE, "Execution timed out"); throw new TimeoutException(ex); } - + stopTimeout(timeoutFuture); throw ex; } - - return proceededInvocationContext; - } - - /** - * Helper method that schedules a thread interrupt after a period of time. - * @param timeoutMillis The time in milliseconds to wait before interrupting. - * @param timedOut A threadlocal that stores whether or not the interrupt has occurred. - * @return A future that can be cancelled if the method execution completes before the interrupt happens - * @throws NamingException If the configured ManagedScheduledExecutorService could not be found - */ - private static Future startTimeout(long timeoutMillis) throws NamingException { - final Thread thread = Thread.currentThread(); - - Runnable timeoutTask = () -> { - thread.interrupt(); - }; - - FaultToleranceService faultToleranceService = Globals.getDefaultBaseServiceLocator() - .getService(FaultToleranceService.class); - ManagedScheduledExecutorService managedScheduledExecutorService = faultToleranceService. - getManagedScheduledExecutorService(); - return managedScheduledExecutorService.schedule(timeoutTask, timeoutMillis, TimeUnit.MILLISECONDS); + return resultValue; } /** diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/fallback/FallbackPolicy.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/fallback/FallbackPolicy.java index ed1c16681bf..2f6926fd879 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/fallback/FallbackPolicy.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/fallback/FallbackPolicy.java @@ -39,114 +39,77 @@ */ package fish.payara.microprofile.faulttolerance.interceptors.fallback; -import fish.payara.microprofile.faulttolerance.FaultToleranceService; -import static fish.payara.microprofile.faulttolerance.FaultToleranceService.FALLBACK_HANDLER_METHOD_NAME; +import fish.payara.microprofile.faulttolerance.FaultToleranceConfig; +import fish.payara.microprofile.faulttolerance.FaultToleranceExecution; +import fish.payara.microprofile.faulttolerance.FaultToleranceMetrics; import fish.payara.microprofile.faulttolerance.cdi.FaultToleranceCdiUtils; import java.lang.reflect.Method; -import java.util.Optional; import java.util.logging.Level; import java.util.logging.Logger; import javax.interceptor.InvocationContext; -import fish.payara.notification.requesttracing.RequestTraceSpan; -import org.eclipse.microprofile.config.Config; import org.eclipse.microprofile.faulttolerance.ExecutionContext; import org.eclipse.microprofile.faulttolerance.Fallback; import org.eclipse.microprofile.faulttolerance.FallbackHandler; import javax.enterprise.inject.spi.CDI; -import org.eclipse.microprofile.config.ConfigProvider; -import org.eclipse.microprofile.metrics.MetricRegistry; -import org.glassfish.api.invocation.InvocationManager; -import org.glassfish.internal.api.Globals; /** * Class that executes the fallback policy defined by the {@link Fallback} annotation. * @author Andrew Pielage */ public class FallbackPolicy { - + private static final Logger logger = Logger.getLogger(FallbackPolicy.class.getName()); - + private final Class> fallbackClass; private final String fallbackMethod; - - @SuppressWarnings("unchecked") - public FallbackPolicy(Fallback fallback, Config config, InvocationContext invocationContext) - throws ClassNotFoundException { - Optional className = FaultToleranceCdiUtils.getOverrideValue(config, Fallback.class, "value", - invocationContext, String.class); - fallbackClass = className.isPresent() - ? (Class>) Thread.currentThread().getContextClassLoader() - .loadClass(className.get()) - : fallback.value(); - fallbackMethod = FaultToleranceCdiUtils.getOverrideValue(config, Fallback.class, - "fallbackMethod", invocationContext, String.class) - .orElse(fallback.fallbackMethod()); + private final FaultToleranceExecution execution; + private final FaultToleranceMetrics metrics; + + public FallbackPolicy(Fallback fallback, FaultToleranceConfig config, FaultToleranceExecution execution, FaultToleranceMetrics metrics, + InvocationContext context) { + this.execution = execution; + this.metrics = metrics; + this.fallbackClass = config.value(fallback, context); + this.fallbackMethod = config.fallbackMethod(fallback, context); } - + /** * Performs the fallback operation defined by the @Fallback annotation. - * @param invocationContext The failing invocation context + * @param context The failing invocation context * @return The result of the executed fallback method * @throws Exception If the fallback method itself fails. */ - public Object fallback(InvocationContext invocationContext, Throwable exception) throws Exception { - Object fallbackInvocationContext = null; - - FaultToleranceService faultToleranceService = - Globals.getDefaultBaseServiceLocator().getService(FaultToleranceService.class); - InvocationManager invocationManager = Globals.getDefaultBaseServiceLocator() - .getService(InvocationManager.class); - faultToleranceService.startFaultToleranceSpan(new RequestTraceSpan("executeFallbackMethod"), - invocationManager, invocationContext); - - MetricRegistry metricRegistry = CDI.current().select(MetricRegistry.class).get(); - String fullMethodSignature = FaultToleranceCdiUtils.getFullAnnotatedMethodSignature(invocationContext, - Fallback.class); - String appName = faultToleranceService.getApplicationName(invocationManager, invocationContext); - - Config config = null; - try { - config = ConfigProvider.getConfig(); - } catch (IllegalArgumentException ex) { - logger.log(Level.INFO, "No config could be found", ex); - } - + public Object fallback(InvocationContext context, Throwable exception) throws Exception { + Object resultValue = null; + execution.startTrace("executeFallbackMethod", context); try { if (fallbackMethod != null && !fallbackMethod.isEmpty()) { logger.log(Level.FINE, "Using fallback method: {0}", fallbackMethod); - fallbackInvocationContext = FaultToleranceCdiUtils - .getAnnotatedMethodClass(invocationContext, Fallback.class) - .getDeclaredMethod(fallbackMethod, invocationContext.getMethod().getParameterTypes()) - .invoke(invocationContext.getTarget(), invocationContext.getParameters()); - faultToleranceService.incrementCounterMetric(metricRegistry, - "ft." + fullMethodSignature + ".fallback.calls.total", appName, config); + resultValue = FaultToleranceCdiUtils + .getAnnotatedMethodClass(context, Fallback.class) + .getDeclaredMethod(fallbackMethod, context.getMethod().getParameterTypes()) + .invoke(context.getTarget(), context.getParameters()); } else { logger.log(Level.FINE, "Using fallback class: {0}", fallbackClass.getName()); - ExecutionContext executionContext = new FaultToleranceExecutionContext(invocationContext.getMethod(), - invocationContext.getParameters(), exception); + ExecutionContext executionContext = new FaultToleranceExecutionContext(context.getMethod(), + context.getParameters(), exception); - fallbackInvocationContext = fallbackClass - .getDeclaredMethod(FALLBACK_HANDLER_METHOD_NAME, ExecutionContext.class) - .invoke(CDI.current().select(fallbackClass).get(), executionContext); - faultToleranceService.incrementCounterMetric(metricRegistry, - "ft." + fullMethodSignature + ".fallback.calls.total", appName, config); + resultValue = CDI.current().select(fallbackClass).get().handle(executionContext); } + metrics.incrementFallbackCallsTotal(context); } catch (Exception ex) { // Increment the failure counter metric - faultToleranceService.incrementCounterMetric(metricRegistry, - "ft." + fullMethodSignature + ".invocations.failed.total", appName, config); - + metrics.incrementInvocationsFailedTotal(Fallback.class, context); throw ex; } finally { - faultToleranceService.endFaultToleranceSpan(); + execution.endTrace(); } - - return fallbackInvocationContext; + return resultValue; } - + /** * Default implementation class for the Fault Tolerance ExecutionContext interface */ @@ -155,13 +118,13 @@ private class FaultToleranceExecutionContext implements ExecutionContext { private final Method method; private final Object[] parameters; private final Throwable failure; - + public FaultToleranceExecutionContext(Method method, Object[] parameters, Throwable failure) { this.method = method; this.parameters = parameters; this.failure = failure; } - + @Override public Method getMethod() { return method; diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/model/AsynchronousPolicy.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/model/AsynchronousPolicy.java new file mode 100644 index 00000000000..07f0385263e --- /dev/null +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/model/AsynchronousPolicy.java @@ -0,0 +1,39 @@ +package fish.payara.microprofile.faulttolerance.model; + +import java.util.concurrent.CompletionStage; +import java.util.concurrent.Future; + +import javax.interceptor.InvocationContext; + +import org.eclipse.microprofile.faulttolerance.Asynchronous; +import org.eclipse.microprofile.faulttolerance.exceptions.FaultToleranceDefinitionException; + +import fish.payara.microprofile.faulttolerance.FaultToleranceConfig; + +/** + * The resolved "cached" information of a {@link Asynchronous} annotation an a specific method. + */ +public final class AsynchronousPolicy extends Policy { + + private static final AsynchronousPolicy IS_ASYNCHRONOUS = new AsynchronousPolicy(); + + private AsynchronousPolicy() { + // hide + } + + public static AsynchronousPolicy create(InvocationContext context, FaultToleranceConfig config) { + if (config.isAnnotationPresent(Asynchronous.class, context) && config.isEnabled(Asynchronous.class, context)) { + checkReturnsFutureOrCompletionStage(context); + return IS_ASYNCHRONOUS; + } + return null; + } + + private static void checkReturnsFutureOrCompletionStage(InvocationContext context) { + Class returnType = context.getMethod().getReturnType(); + if (returnType != Future.class && returnType != CompletionStage.class) { + throw new FaultToleranceDefinitionException(describe(context.getMethod(), Asynchronous.class, "") + + "does not return a Future or CompletionStage but: " + returnType.getName()); + } + } +} diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/model/BulkheadPolicy.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/model/BulkheadPolicy.java new file mode 100644 index 00000000000..0fb56e3150d --- /dev/null +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/model/BulkheadPolicy.java @@ -0,0 +1,35 @@ +package fish.payara.microprofile.faulttolerance.model; + +import java.lang.reflect.Method; + +import javax.interceptor.InvocationContext; + +import org.eclipse.microprofile.faulttolerance.Bulkhead; + +import fish.payara.microprofile.faulttolerance.FaultToleranceConfig; + +/** + * The resolved "cached" information of a {@link Bulkhead} annotation an a specific method. + */ +public final class BulkheadPolicy extends Policy { + + public final int value; + public final int waitingTaskQueue; + + public BulkheadPolicy(Method annotatedMethod, int value, int waitingTaskQueue) { + checkAtLeast(1, annotatedMethod, Bulkhead.class, "value", value); + checkAtLeast(0, annotatedMethod, Bulkhead.class, "waitingTaskQueue", waitingTaskQueue); + this.value = value; + this.waitingTaskQueue = waitingTaskQueue; + } + + public static BulkheadPolicy create(InvocationContext context, FaultToleranceConfig config) { + if (config.isAnnotationPresent(Bulkhead.class, context) && config.isEnabled(Bulkhead.class, context)) { + Bulkhead annotation = config.getAnnotation(Bulkhead.class, context); + return new BulkheadPolicy(context.getMethod(), + config.value(annotation, context), + config.waitingTaskQueue(annotation, context)); + } + return null; + } +} diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/model/CircuitBreakerPolicy.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/model/CircuitBreakerPolicy.java new file mode 100644 index 00000000000..a5d141c1ef9 --- /dev/null +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/model/CircuitBreakerPolicy.java @@ -0,0 +1,52 @@ +package fish.payara.microprofile.faulttolerance.model; + +import java.lang.reflect.Method; +import java.time.temporal.ChronoUnit; + +import javax.interceptor.InvocationContext; + +import org.eclipse.microprofile.faulttolerance.CircuitBreaker; + +import fish.payara.microprofile.faulttolerance.FaultToleranceConfig; + +/** + * The resolved "cached" information of a {@link CircuitBreaker} annotation an a specific method. + */ +public final class CircuitBreakerPolicy extends Policy { + + public final Class[] failOn; + public final long delay; + public final ChronoUnit delayUnit; + public final int requestVolumeThreshold; + public final double failureRatio; + public final int successThreshold; + + public CircuitBreakerPolicy(Method annotatedMethod, Class[] failOn, long delay, ChronoUnit delayUnit, + int requestVolumeThreshold, double failureRatio, int successThreshold) { + checkAtLeast(0, annotatedMethod, CircuitBreaker.class, "delay", delay); + checkAtLeast(1, annotatedMethod, CircuitBreaker.class, "requestVolumeThreshold", requestVolumeThreshold); + checkAtLeast(0d, annotatedMethod, CircuitBreaker.class, "failureRatio", failureRatio); + checkAtMost(1.0d, annotatedMethod, CircuitBreaker.class, "failureRatio", failureRatio); + checkAtLeast(1, annotatedMethod, CircuitBreaker.class, "successThreshold", successThreshold); + this.failOn = failOn; + this.delay = delay; + this.delayUnit = delayUnit; + this.requestVolumeThreshold = requestVolumeThreshold; + this.failureRatio = failureRatio; + this.successThreshold = successThreshold; + } + + public static CircuitBreakerPolicy create(InvocationContext context, FaultToleranceConfig config) { + if (config.isAnnotationPresent(CircuitBreaker.class, context) && config.isEnabled(CircuitBreaker.class, context)) { + CircuitBreaker annotation = config.getAnnotation(CircuitBreaker.class, context); + return new CircuitBreakerPolicy(context.getMethod(), + config.failOn(annotation, context), + config.delay(annotation, context), + config.delayUnit(annotation, context), + config.requestVolumeThreshold(annotation, context), + config.failureRatio(annotation, context), + config.successThreshold(annotation, context)); + } + return null; + } +} diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/model/FallbackPolicy.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/model/FallbackPolicy.java new file mode 100644 index 00000000000..a515eded3d2 --- /dev/null +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/model/FallbackPolicy.java @@ -0,0 +1,50 @@ +package fish.payara.microprofile.faulttolerance.model; + +import java.lang.reflect.Method; + +import javax.interceptor.InvocationContext; + +import org.eclipse.microprofile.faulttolerance.ExecutionContext; +import org.eclipse.microprofile.faulttolerance.Fallback; +import org.eclipse.microprofile.faulttolerance.FallbackHandler; +import org.eclipse.microprofile.faulttolerance.exceptions.FaultToleranceDefinitionException; + +import fish.payara.microprofile.faulttolerance.FaultToleranceConfig; + +/** + * The resolved "cached" information of a {@link Fallback} annotation an a specific method. + */ +public final class FallbackPolicy extends Policy { + + public final Class> value; + public final String fallbackMethod; + + public FallbackPolicy(Method method, Class> value, String fallbackMethod) { + checkUnambiguous(value, fallbackMethod); + if (fallbackMethod != null && !fallbackMethod.isEmpty()) { + checkReturnsSameAs(method, Fallback.class, "fallbackMethod", method.getDeclaringClass(), fallbackMethod, + method.getParameterTypes()); + } + if (value != null && value != Fallback.DEFAULT.class) { + checkReturnsSameAs(method, Fallback.class, "value", value, "handle", ExecutionContext.class); + } + this.value = value; + this.fallbackMethod = fallbackMethod; + } + + public static FallbackPolicy create(InvocationContext context, FaultToleranceConfig config) { + if (config.isAnnotationPresent(Fallback.class, context) && config.isEnabled(Fallback.class, context)) { + Fallback annotation = config.getAnnotation(Fallback.class, context); + return new FallbackPolicy(context.getMethod(), + config.value(annotation, context), + config.fallbackMethod(annotation, context)); + } + return null; + } + + private static void checkUnambiguous(Class> value, String fallbackMethod) { + if (fallbackMethod != null && !fallbackMethod.isEmpty() && value != null && value != Fallback.DEFAULT.class) { + throw new FaultToleranceDefinitionException("Both a fallback class and method have been set."); + } + } +} diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/model/FaultToleranceBehaviour.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/model/FaultToleranceBehaviour.java new file mode 100644 index 00000000000..6cb4b3adab9 --- /dev/null +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/model/FaultToleranceBehaviour.java @@ -0,0 +1,32 @@ +package fish.payara.microprofile.faulttolerance.model; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import javax.interceptor.InterceptorBinding; + +import org.eclipse.microprofile.faulttolerance.Fallback; + +/** + * Added to methods at runtime in case they are affected by one of the FT annotations. + * This means a FT annotation is either present directly on the method or on the class declaring the method. + * + * This indirection is needed for two reasons: + * + * 1) Allow to process all FT annotations with a single interceptor + * + * 2) Allow to process {@link Fallback} even though it cannot be annotated on type level what would be needed to bind it + * to an interceptor directly. + * + * @author Jan Bernitt + */ +@Inherited +@InterceptorBinding +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.METHOD, ElementType.TYPE }) +public @interface FaultToleranceBehaviour { + //marker +} diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/model/FaultTolerancePolicy.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/model/FaultTolerancePolicy.java new file mode 100644 index 00000000000..63e0d9c5813 --- /dev/null +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/model/FaultTolerancePolicy.java @@ -0,0 +1,122 @@ +package fish.payara.microprofile.faulttolerance.model; + +import java.io.Serializable; +import java.lang.reflect.Method; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Supplier; + +import javax.interceptor.InvocationContext; + +import org.eclipse.microprofile.faulttolerance.exceptions.FaultToleranceDefinitionException; + +import fish.payara.microprofile.faulttolerance.FaultToleranceConfig; + +/** + * The {@link FaultTolerancePolicy} describes the effective aggregated policies to use for a particular {@link Method} + * when adding fault tolerant behaviour to it. + * + * The policies are extracted from FT annotations and the {@link FaultToleranceConfig}. + * + * In contrast to the plain annotations the policies do consider configuration overrides and include validation of the + * effective values. + * + * The policy class also reduces the need to analyse FT annotations for each invocation and works as a consistent source + * of truth throughout the execution of FT behaviour that is convenient to pass around as a single immutable value. + * + * @author Jan Bernitt + */ +public final class FaultTolerancePolicy implements Serializable { + + private static final long TTL = 60 * 1000; + + private static final ConcurrentHashMap POLICY_BY_METHOD = new ConcurrentHashMap<>(); + + public static void clean() { + long now = System.currentTimeMillis(); + POLICY_BY_METHOD.entrySet().removeIf(entry -> now > entry.getValue().expiresMillis); + } + + /** + * Returns the {@link FaultTolerancePolicy} to use for the method invoked in the current context. + * + * @param context current context + * @param configSpplier supplies the configuration (if needed, in case returned policy needs to be created with help + * of the {@link FaultToleranceConfig}) + * @return the policy to apply + * @throws FaultToleranceDefinitionException in case the effective policy contains illegal values + */ + public static FaultTolerancePolicy get(InvocationContext context, Supplier configSpplier) + throws FaultToleranceDefinitionException { + return POLICY_BY_METHOD.compute(context.getMethod(), (method, info) -> + info != null && !info.isExpired() ? info : create(context, configSpplier)); + } + + private static FaultTolerancePolicy create(InvocationContext context, Supplier configSpplier) { + FaultToleranceConfig config = configSpplier.get(); + boolean isFaultToleranceEnabled = config.isEnabled(context); + if (!isFaultToleranceEnabled) { + return new FaultTolerancePolicy(false, false, null, null, null, null, null, null); + } + return new FaultTolerancePolicy(isFaultToleranceEnabled, + config.isMetricsEnabled(context), + AsynchronousPolicy.create(context, config), + BulkheadPolicy.create(context, config), + CircuitBreakerPolicy.create(context, config), + FallbackPolicy.create(context, config), + RetryPolicy.create(context, config), + TimeoutPolicy.create(context, config)); + } + + private final long expiresMillis; + public final boolean isFaultToleranceEnabled; + public final boolean isMetricsEnabled; + public final AsynchronousPolicy asynchronous; + public final BulkheadPolicy bulkhead; + public final CircuitBreakerPolicy circuitBreaker; + public final FallbackPolicy fallback; + public final RetryPolicy retry; + public final TimeoutPolicy timeout; + + public FaultTolerancePolicy(boolean isFaultToleranceEnabled, boolean isMetricsEnabled, AsynchronousPolicy asynchronous, + BulkheadPolicy bulkhead, CircuitBreakerPolicy circuitBreaker, FallbackPolicy fallback, RetryPolicy retry, + TimeoutPolicy timeout) { + this.expiresMillis = System.currentTimeMillis() + TTL; + this.isFaultToleranceEnabled = isFaultToleranceEnabled; + this.isMetricsEnabled = isMetricsEnabled; + this.asynchronous = asynchronous; + this.bulkhead = bulkhead; + this.circuitBreaker = circuitBreaker; + this.fallback = fallback; + this.retry = retry; + this.timeout = timeout; + } + + private boolean isExpired() { + return System.currentTimeMillis() > expiresMillis; + } + + public boolean isAsynchronous() { + return asynchronous != null; + } + + public boolean isBulkheadPresent() { + return bulkhead != null; + } + + public boolean isCircuitBreakerPresent() { + return circuitBreaker != null; + } + + public boolean isFallbackPresent() { + return fallback != null; + } + + public boolean isRetryPresent() { + return retry != null; + } + + public boolean isTimeoutPresent() { + return timeout != null; + } + +} diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/model/Policy.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/model/Policy.java new file mode 100644 index 00000000000..59b95c3fb4c --- /dev/null +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/model/Policy.java @@ -0,0 +1,66 @@ +package fish.payara.microprofile.faulttolerance.model; + +import java.io.Serializable; +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; + +import org.eclipse.microprofile.faulttolerance.exceptions.FaultToleranceDefinitionException; + +public abstract class Policy implements Serializable { + + public static void checkAtLeast(long minimum, Method annotatedMethod, Class annotationType, + String attribute, long value) { + if (value < minimum) { + throw new FaultToleranceDefinitionException(describe(annotatedMethod, annotationType, attribute) + + "value less than " + minimum + ": " + value); + } + } + + public static void checkAtLeast(double minimum, Method annotatedMethod, Class annotationType, + String attribute, double value) { + if (value < minimum) { + throw new FaultToleranceDefinitionException(describe(annotatedMethod, annotationType, attribute) + + "value less than " + minimum + ": " + value); + } + } + + public static void checkAtLeast(String attribute1, long minimum, Method annotatedMethod, + Class annotationType, String attribute2, long value) { + if (value < minimum) { + throw new FaultToleranceDefinitionException(describe(annotatedMethod, annotationType, attribute2) + + "value less than or equal to the " + attribute(attribute1) + "value: " + value); + } + } + + public static void checkAtMost(double maximum, Method annotatedMethod, Class annotationType, + String attribute, double value) { + if (value > maximum) { + throw new FaultToleranceDefinitionException(describe(annotatedMethod, annotationType, attribute) + + "value greater than " + maximum + ": " + value); + } + } + + public static void checkReturnsSameAs(Method annotatedMethod, Class annotationType, + String attribute, Class valueType, String valueMethodName, Class... valueParameterTypes) { + try { + Method actual = valueType.getDeclaredMethod(valueMethodName, valueParameterTypes); + if (actual.getReturnType() != annotatedMethod.getReturnType()) { + throw new FaultToleranceDefinitionException(describe(annotatedMethod, annotationType, attribute) + + "whose return type of does not match."); + } + } catch (NoSuchMethodException ex) { + throw new FaultToleranceDefinitionException(describe(annotatedMethod, annotationType, attribute) + + "refering to a method "+valueMethodName+" that does not exist for type: " + valueType.getName(), ex); + } + } + + protected static String describe(Method annotatedMethod, Class annotationType, String attribute) { + return "Method \"" + annotatedMethod.getName() + "\" in " + annotatedMethod.getDeclaringClass().getName() + + " annotated with " + annotationType.getCanonicalName() + + (attribute.isEmpty() ? " " : " has a " + attribute(attribute)); + } + + private static String attribute(String attribute) { + return "value".equals(attribute) ? "" : attribute + " "; + } +} diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/model/RetryPolicy.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/model/RetryPolicy.java new file mode 100644 index 00000000000..5cd60a09123 --- /dev/null +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/model/RetryPolicy.java @@ -0,0 +1,63 @@ +package fish.payara.microprofile.faulttolerance.model; + +import java.lang.reflect.Method; +import java.time.temporal.ChronoUnit; + +import javax.interceptor.InvocationContext; + +import org.eclipse.microprofile.faulttolerance.Retry; + +import fish.payara.microprofile.faulttolerance.FaultToleranceConfig; + +/** + * The resolved "cached" information of a {@link Retry} annotation an a specific method. + */ +public final class RetryPolicy extends Policy { + + public final int maxRetries; + public final long delay; + public final ChronoUnit delayUnit; + public final long maxDuration; + public final ChronoUnit durationUnit; + public final long jitter; + public final ChronoUnit jitterDelayUnit; + public final Class[] retryOn; + public final Class[] abortOn; + + public RetryPolicy(Method annotatedMethod, int maxRetries, long delay, ChronoUnit delayUnit, long maxDuration, ChronoUnit durationUnit, + long jitter, ChronoUnit jitterDelayUnit, Class[] retryOn, + Class[] abortOn) { + checkAtLeast(-1, annotatedMethod, Retry.class, "maxRetries", maxRetries); + checkAtLeast(0, annotatedMethod, Retry.class, "delay", delay); + checkAtLeast(0, annotatedMethod, Retry.class, "maxDuration", maxDuration); + checkAtLeast("delay", delay + 1, annotatedMethod, Retry.class, "maxDuration", maxDuration); + checkAtLeast(0, annotatedMethod, Retry.class, "jitter", jitter); + this.maxRetries = maxRetries; + this.delay = delay; + this.delayUnit = delayUnit; + this.maxDuration = maxDuration; + this.durationUnit = durationUnit; + this.jitter = jitter; + this.jitterDelayUnit = jitterDelayUnit; + this.retryOn = retryOn; + this.abortOn = abortOn; + } + + public static RetryPolicy create(InvocationContext context, FaultToleranceConfig config) { + if (config.isAnnotationPresent(Retry.class, context) && config.isEnabled(Retry.class, context)) { + Retry annotation = config.getAnnotation(Retry.class, context); + return new RetryPolicy(context.getMethod(), + config.maxRetries(annotation, context), + config.delay(annotation, context), + config.delayUnit(annotation, context), + config.maxDuration(annotation, context), + config.durationUnit(annotation, context), + config.jitter(annotation, context), + config.jitterDelayUnit(annotation, context), + config.retryOn(annotation, context), + config.abortOn(annotation, context)); + } + return null; + } + +} diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/model/TimeoutPolicy.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/model/TimeoutPolicy.java new file mode 100644 index 00000000000..841d9672b0c --- /dev/null +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/model/TimeoutPolicy.java @@ -0,0 +1,35 @@ +package fish.payara.microprofile.faulttolerance.model; + +import java.lang.reflect.Method; +import java.time.temporal.ChronoUnit; + +import javax.interceptor.InvocationContext; + +import org.eclipse.microprofile.faulttolerance.Timeout; + +import fish.payara.microprofile.faulttolerance.FaultToleranceConfig; + +/** + * The resolved "cached" information of a {@link Timeout} annotation an a specific method. + */ +public final class TimeoutPolicy extends Policy { + + public final long value; + public final ChronoUnit unit; + + public TimeoutPolicy(Method annotatedMethod, long value, ChronoUnit unit) { + checkAtLeast(0, annotatedMethod, Timeout.class, "value", value); + this.value = value; + this.unit = unit; + } + + public static TimeoutPolicy create(InvocationContext context, FaultToleranceConfig config) { + if (config.isAnnotationPresent(Timeout.class, context) && config.isEnabled(Timeout.class, context)) { + Timeout annotation = config.getAnnotation(Timeout.class, context); + return new TimeoutPolicy(context.getMethod(), + config.value(annotation, context), + config.unit(annotation, context)); + } + return null; + } +} diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/state/BulkheadSemaphore.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/state/BulkheadSemaphore.java new file mode 100644 index 00000000000..e0332d1e3f9 --- /dev/null +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/state/BulkheadSemaphore.java @@ -0,0 +1,27 @@ +package fish.payara.microprofile.faulttolerance.state; + +import java.util.concurrent.Semaphore; + +public final class BulkheadSemaphore extends Semaphore { + + final int totalPermits; + + public BulkheadSemaphore(int permits) { + super(permits, true); + this.totalPermits = permits; + } + + public int getTotalPermits() { + return totalPermits; + } + + /** + * Note that the number of acquired permits is only correct if {@link #acquire()} and {@link #release()} are used + * consistent. + * + * @return number of currently acquire permits + */ + public int acquiredPermits() { + return totalPermits - availablePermits(); + } +} diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/state/CircuitBreakerState.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/state/CircuitBreakerState.java index fed92feedcd..a80507ba02c 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/state/CircuitBreakerState.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/state/CircuitBreakerState.java @@ -170,8 +170,20 @@ public boolean isOverFailureThreshold(long failureThreshold) { */ public long updateAndGet(CircuitState circuitState) { return this.currentStateTime.is(circuitState) - ? this.currentStateTime.update() - : this.allStateTimes.get(circuitState).nanos(); + ? this.currentStateTime.update() + : this.allStateTimes.get(circuitState).nanos(); + } + + public long open() { + return updateAndGet(CircuitState.OPEN); + } + + public long halfOpen() { + return updateAndGet(CircuitState.HALF_OPEN); + } + + public long closed() { + return updateAndGet(CircuitState.CLOSED); } } diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/validators/AsynchronousValidator.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/validators/AsynchronousValidator.java index 67bdfa36025..55838257e39 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/validators/AsynchronousValidator.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/validators/AsynchronousValidator.java @@ -39,6 +39,7 @@ */ package fish.payara.microprofile.faulttolerance.validators; +import java.util.concurrent.CompletionStage; import java.util.concurrent.Future; import javax.enterprise.inject.spi.AnnotatedMethod; import org.eclipse.microprofile.faulttolerance.Asynchronous; @@ -51,14 +52,16 @@ public class AsynchronousValidator { /** - * Validates the given @Asynchronous annotation. + * Validates the given {@link Asynchronous} annotation. * @param asynchronous The annotation to validate * @param annotatedMethod The annotated method to validate */ public static void validateAnnotation(Asynchronous asynchronous, AnnotatedMethod annotatedMethod) { - if (annotatedMethod.getJavaMember().getReturnType() != Future.class) { + Class returnType = annotatedMethod.getJavaMember().getReturnType(); + if (returnType != Future.class && returnType != CompletionStage.class) { throw new FaultToleranceDefinitionException("Method \"" + annotatedMethod.getJavaMember().getName() + "\"" - + " annotated with " + Asynchronous.class.getCanonicalName() + " does not return a Future."); + + " annotated with " + Asynchronous.class.getCanonicalName() + + " does not return a Future or CompletionStage. Note that subtypes of these two are not permitted."); } } } diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/validators/FallbackValidator.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/validators/FallbackValidator.java index e50acdc68c1..a355d9e19f5 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/validators/FallbackValidator.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/validators/FallbackValidator.java @@ -39,8 +39,6 @@ */ package fish.payara.microprofile.faulttolerance.validators; -import static fish.payara.microprofile.faulttolerance.FaultToleranceService.FALLBACK_HANDLER_METHOD_NAME; - import java.util.Optional; import fish.payara.microprofile.faulttolerance.cdi.FaultToleranceCdiUtils; @@ -98,7 +96,7 @@ public static void validateAnnotation(Fallback fallback, AnnotatedMethod anno throw new FaultToleranceDefinitionException("Could not find fallback method: " + fallbackMethod, ex); } } else if (fallbackClass != null && fallbackClass != Fallback.DEFAULT.class) { - if (fallbackClass.getDeclaredMethod(FALLBACK_HANDLER_METHOD_NAME, ExecutionContext.class).getReturnType() + if (fallbackClass.getDeclaredMethod("handle", ExecutionContext.class).getReturnType() != annotatedMethod.getJavaMember().getReturnType()) { throw new FaultToleranceDefinitionException( "Return type of fallback class handle method does not match."); From be16c143f93991524d94c7f2bc23d355253554a1 Mon Sep 17 00:00:00 2001 From: Jan Bernitt Date: Thu, 11 Apr 2019 19:32:12 +0200 Subject: [PATCH 04/30] PAYARA-3468 TCK passes --- .../FaultToleranceExecution.java | 17 +- .../FaultToleranceExecutionContext.java | 36 ++ .../faulttolerance/FaultToleranceService.java | 405 +++++------------- .../cdi/FaultToleranceCDIExtension.java | 5 +- .../interceptors/AsynchronousInterceptor.java | 77 +--- .../BaseFaultToleranceInterceptor.java | 1 - .../CircuitBreakerInterceptor.java | 10 +- .../FaultToleranceBehaviour.java | 2 +- .../FaultToleranceInterceptor.java | 30 +- .../interceptors/TimeoutInterceptor.java | 2 +- .../interceptors/fallback/FallbackPolicy.java | 34 +- .../model/FaultTolerancePolicy.java | 122 ------ .../{model => policy}/AsynchronousPolicy.java | 8 +- .../{model => policy}/BulkheadPolicy.java | 2 +- .../CircuitBreakerPolicy.java | 15 +- .../{model => policy}/FallbackPolicy.java | 18 +- .../policy/FaultTolerancePolicy.java | 401 +++++++++++++++++ .../{model => policy}/Policy.java | 24 +- .../{model => policy}/RetryPolicy.java | 44 +- .../{model => policy}/TimeoutPolicy.java | 7 +- .../state/CircuitBreakerState.java | 40 +- 21 files changed, 721 insertions(+), 579 deletions(-) create mode 100644 appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/FaultToleranceExecutionContext.java rename appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/{model => interceptors}/FaultToleranceBehaviour.java (94%) delete mode 100644 appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/model/FaultTolerancePolicy.java rename appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/{model => policy}/AsynchronousPolicy.java (83%) rename appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/{model => policy}/BulkheadPolicy.java (95%) rename appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/{model => policy}/CircuitBreakerPolicy.java (81%) rename appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/{model => policy}/FallbackPolicy.java (79%) create mode 100644 appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/FaultTolerancePolicy.java rename appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/{model => policy}/Policy.java (77%) rename appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/{model => policy}/RetryPolicy.java (58%) rename appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/{model => policy}/TimeoutPolicy.java (87%) diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/FaultToleranceExecution.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/FaultToleranceExecution.java index ef44f9b3f7e..a344595fad6 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/FaultToleranceExecution.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/FaultToleranceExecution.java @@ -1,9 +1,13 @@ package fish.payara.microprofile.faulttolerance; +import java.util.concurrent.Callable; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.Future; import javax.interceptor.InvocationContext; +import org.eclipse.microprofile.faulttolerance.FallbackHandler; + import fish.payara.microprofile.faulttolerance.state.BulkheadSemaphore; import fish.payara.microprofile.faulttolerance.state.CircuitBreakerState; @@ -11,22 +15,25 @@ public interface FaultToleranceExecution { CircuitBreakerState getState(int requestVolumeThreshold, InvocationContext context); - void scheduleHalfOpen(long delayMillis, CircuitBreakerState circuitBreakerState) throws Exception; - BulkheadSemaphore getExecutionSemaphoreOf(int maxConcurrentThreads, InvocationContext context); BulkheadSemaphore getWaitingQueueSemaphoreOf(int queueCapacity, InvocationContext context); - Future runAsynchronous(InvocationContext context) throws Exception; + void delay(long delayMillis, InvocationContext context) throws InterruptedException; - //completeAsynchronous + void runAsynchronous(CompletableFuture asyncResult, Callable operation) throws Exception; /** * @return A future that can be cancelled if the method execution completes before the interrupt happens */ - Future timeoutIn(long timeoutMillis) throws Exception; + Future scheduleDelayed(long delayMillis, Runnable operation) throws Exception; + + Object fallbackHandle(Class> fallbackClass, InvocationContext context, Exception exception) throws Exception; + + Object fallbackInvoke(String fallbackMethod, InvocationContext context) throws Exception; void startTrace(String method, InvocationContext context); void endTrace(); + } diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/FaultToleranceExecutionContext.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/FaultToleranceExecutionContext.java new file mode 100644 index 00000000000..a13f29eb8ed --- /dev/null +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/FaultToleranceExecutionContext.java @@ -0,0 +1,36 @@ +package fish.payara.microprofile.faulttolerance; + +import java.lang.reflect.Method; + +import org.eclipse.microprofile.faulttolerance.ExecutionContext; + +/** + * Default implementation class for the Fault Tolerance ExecutionContext interface + */ +public final class FaultToleranceExecutionContext implements ExecutionContext { + + private final Method method; + private final Object[] parameters; + private final Throwable failure; + + public FaultToleranceExecutionContext(Method method, Object[] parameters, Throwable failure) { + this.method = method; + this.parameters = parameters; + this.failure = failure; + } + + @Override + public Method getMethod() { + return method; + } + + @Override + public Object[] getParameters() { + return parameters; + } + + @Override + public Throwable getFailure() { + return failure; + } +} \ No newline at end of file diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/FaultToleranceService.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/FaultToleranceService.java index 51963ceb89f..36bef4a3801 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/FaultToleranceService.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/FaultToleranceService.java @@ -39,15 +39,21 @@ */ package fish.payara.microprofile.faulttolerance; +import fish.payara.microprofile.faulttolerance.cdi.FaultToleranceCdiUtils; +import fish.payara.microprofile.faulttolerance.policy.AsynchronousPolicy; import fish.payara.microprofile.faulttolerance.state.BulkheadSemaphore; import fish.payara.microprofile.faulttolerance.state.CircuitBreakerState; import fish.payara.notification.requesttracing.RequestTraceSpan; import fish.payara.nucleus.requesttracing.RequestTracingService; +import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Arrays; import java.util.Map; +import java.util.concurrent.Callable; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.logging.Level; @@ -55,11 +61,16 @@ import javax.annotation.PostConstruct; import javax.enterprise.concurrent.ManagedExecutorService; import javax.enterprise.concurrent.ManagedScheduledExecutorService; +import javax.enterprise.inject.spi.CDI; import javax.inject.Inject; import javax.inject.Named; import javax.interceptor.InvocationContext; import javax.naming.InitialContext; import javax.naming.NamingException; + +import org.eclipse.microprofile.faulttolerance.Fallback; +import org.eclipse.microprofile.faulttolerance.FallbackHandler; +import org.eclipse.microprofile.faulttolerance.exceptions.FaultToleranceDefinitionException; import org.glassfish.api.StartupRunLevel; import org.glassfish.api.admin.ServerEnvironment; import org.glassfish.api.event.EventListener; @@ -104,13 +115,19 @@ public class FaultToleranceService implements EventListener, FaultToleranceExecu private Events events; private final Map stateByApplication = new ConcurrentHashMap<>(); - + private ManagedScheduledExecutorService defaultScheduledExecutorService; + private ManagedExecutorService defaultExecutorService; + @PostConstruct - public void postConstruct() { + public void postConstruct() throws NamingException { events.register(this); serviceConfig = serviceLocator.getService(FaultToleranceServiceConfiguration.class); invocationManager = serviceLocator.getService(InvocationManager.class); requestTracingService = serviceLocator.getService(RequestTracingService.class); + InitialContext context = new InitialContext(); + defaultExecutorService = (ManagedExecutorService) context.lookup("java:comp/DefaultManagedExecutorService"); + defaultScheduledExecutorService = (ManagedScheduledExecutorService) context + .lookup("java:comp/DefaultManagedScheduledExecutorService"); } @Override @@ -121,303 +138,53 @@ public void event(Event event) { } } - /** - * Helper method that sets the enabled status for a given application. - * @param applicationName The name of the application to register - * @param serviceConfig The config to check for override values from - */ - private void initialiseFaultToleranceObject(String applicationName) { - // Double lock as multiple methods can get inside the calling if at the same time - logger.log(Level.FINER, "Checking double lock to see if something else has added the application"); - stateByApplication.computeIfAbsent(applicationName, key -> new FaultToleranceApplicationState()); - } - - /** - * Gets the configured ManagedExecutorService. - * @return The configured ManagedExecutorService, or the default ManagedExecutorService if the configured one - * couldn't be found - * @throws NamingException If the default ManagedExecutorService couldn't be found - */ - private ManagedExecutorService getManagedExecutorService() throws NamingException { - String managedExecutorServiceName = serviceConfig.getManagedExecutorService(); - InitialContext ctx = new InitialContext(); - - ManagedExecutorService managedExecutorService; - - // If no name has been set, just get the default - if (managedExecutorServiceName == null || managedExecutorServiceName.isEmpty()) { - managedExecutorService = (ManagedExecutorService) ctx.lookup("java:comp/DefaultManagedExecutorService"); - } else { - try { - managedExecutorService = (ManagedExecutorService) ctx.lookup(managedExecutorServiceName); - } catch (NamingException ex) { - logger.log(Level.INFO, "Could not find configured ManagedExecutorService, " - + managedExecutorServiceName + ", so resorting to default", ex); - managedExecutorService = (ManagedExecutorService) ctx.lookup("java:comp/DefaultManagedExecutorService"); - } - } - - return managedExecutorService; - } - //TODO use the scheduler to schedule a clean of FT Info - /** - * Gets the configured ManagedScheduledExecutorService. - * @return The configured ManagedExecutorService, or the default ManagedScheduledExecutorService if the configured - * one couldn't be found - * @throws NamingException If the default ManagedScheduledExecutorService couldn't be found - */ - private ManagedScheduledExecutorService getManagedScheduledExecutorService() throws NamingException { - String managedScheduledExecutorServiceName = serviceConfig - .getManagedScheduledExecutorService(); - InitialContext ctx = new InitialContext(); + private ManagedExecutorService getManagedExecutorService() { + return lookup(serviceConfig.getManagedExecutorService(), defaultExecutorService); + } - ManagedScheduledExecutorService managedScheduledExecutorService = null; + private ManagedScheduledExecutorService getManagedScheduledExecutorService() { + return lookup(serviceConfig.getManagedScheduledExecutorService(), defaultScheduledExecutorService); + } + @SuppressWarnings("unchecked") + private static T lookup(String name, T defaultInstance) { // If no name has been set, just get the default - if (managedScheduledExecutorServiceName == null || managedScheduledExecutorServiceName.isEmpty()) { - managedScheduledExecutorService = (ManagedScheduledExecutorService) ctx.lookup( - "java:comp/DefaultManagedScheduledExecutorService"); - } else { - try { - managedScheduledExecutorService = (ManagedScheduledExecutorService) ctx.lookup( - managedScheduledExecutorServiceName); - } catch (NamingException ex) { - logger.log(Level.INFO, "Could not find configured ManagedScheduledExecutorService, " - + managedScheduledExecutorServiceName + ", so resorting to default", ex); - managedScheduledExecutorService = (ManagedScheduledExecutorService) ctx.lookup( - "java:comp/DefaultManagedScheduledExecutorService"); - } + if (name == null || name.isEmpty()) { + return defaultInstance; } - - return managedScheduledExecutorService; - } - - /** - * Gets the Bulkhead Execution Semaphore for a given application method, registering it to the - * FaultToleranceService if it hasn't already. - * @param applicationName The name of the application - * @param invocationTarget The target object obtained from InvocationContext.getTarget() - * @param annotatedMethod The method that's annotated with @Bulkhead - * @param bulkheadValue The value parameter of the Bulkhead annotation - * @return The Semaphore for the given application method. - */ - public BulkheadSemaphore getBulkheadExecutionSemaphore(String applicationName, Object invocationTarget, - Method annotatedMethod, int bulkheadValue) { - BulkheadSemaphore bulkheadExecutionSemaphore; - String fullMethodSignature = getFullMethodSignature(annotatedMethod); - - Map annotatedMethodSemaphores = null; - try { - annotatedMethodSemaphores = stateByApplication.get(applicationName).getBulkheadExecutionSemaphores() - .get(invocationTarget); - } catch (NullPointerException npe) { - logger.log(Level.FINE, "NPE caught trying to get semaphores for annotated method", npe); + return (T) new InitialContext().lookup(name); + } catch (Exception ex) { + logger.log(Level.INFO, "Could not find configured , " + name + ", so resorting to default", ex); + return defaultInstance; } - - // If there isn't a semaphore registered for this bean, register one, otherwise just return - // the one already registered - if (annotatedMethodSemaphores == null) { - logger.log(Level.FINER, "No matching application or bean in bulkhead execution semaphore map, registering..."); - bulkheadExecutionSemaphore = createBulkheadExecutionSemaphore(applicationName, invocationTarget, - fullMethodSignature, bulkheadValue); - } else { - bulkheadExecutionSemaphore = annotatedMethodSemaphores.get(fullMethodSignature); - - // If there isn't a semaphore registered for this method signature, register one, otherwise just return - // the one already registered - if (bulkheadExecutionSemaphore == null) { - logger.log(Level.FINER, "No matching method signature in the bulkhead execution semaphore map, " - + "registering..."); - bulkheadExecutionSemaphore = createBulkheadExecutionSemaphore(applicationName, invocationTarget, - fullMethodSignature, bulkheadValue); - } - } - - return bulkheadExecutionSemaphore; } - /** - * Helper method to create and register a Bulkhead Execution Semaphore for an annotated method - * @param applicationName The name of the application - * @param invocationTarget The target object obtained from InvocationContext.getTarget() - * @param fullMethodSignature The method signature to register the semaphore against - * @param bulkheadValue The size of the bulkhead - * @return The Bulkhead Execution Semaphore for the given method signature and application - */ - private synchronized BulkheadSemaphore createBulkheadExecutionSemaphore(String applicationName, Object invocationTarget, - String fullMethodSignature, int bulkheadValue) { - - // Double lock as multiple methods can get inside the calling if at the same time - logger.log(Level.FINER, "Checking double lock to see if something else has already added the application to " - + "the bulkhead execution semaphore map"); - if (stateByApplication.get(applicationName).getBulkheadExecutionSemaphores().get(invocationTarget) == null) { - logger.log(Level.FINER, "Registering bean to bulkhead execution semaphore map: {0}", - invocationTarget); - - stateByApplication.get(applicationName).getBulkheadExecutionSemaphores().put( - invocationTarget, - new ConcurrentHashMap<>()); - } - - // Double lock as multiple methods can get inside the calling if at the same time - logger.log(Level.FINER, "Checking double lock to see if something else has already added the annotated method " - + "to the bulkhead execution semaphore map"); - if (stateByApplication.get(applicationName).getBulkheadExecutionSemaphores().get(invocationTarget) - .get(fullMethodSignature) == null) { - logger.log(Level.FINER, "Registering semaphore for method {0} to the bulkhead execution semaphore map", fullMethodSignature); - stateByApplication.get(applicationName).getBulkheadExecutionSemaphores().get(invocationTarget) - .put(fullMethodSignature, new BulkheadSemaphore(bulkheadValue)); - } - - return stateByApplication.get(applicationName).getBulkheadExecutionSemaphores().get(invocationTarget) - .get(fullMethodSignature); + private FaultToleranceApplicationState getApplicationState(String applicationName) { + return stateByApplication.computeIfAbsent(applicationName, key -> new FaultToleranceApplicationState()); } - /** - * Gets the Bulkhead Execution Queue Semaphore for a given application method, registering it to the - * FaultToleranceService if it hasn't already. - * @param applicationName The name of the application - * @param invocationTarget The target object obtained from InvocationContext.getTarget() - * @param annotatedMethod The method that's annotated with @Bulkhead and @Asynchronous - * @param bulkheadWaitingTaskQueue The waitingTaskQueue parameter of the Bulkhead annotation - * @return The Semaphore for the given application method. - */ - public BulkheadSemaphore getBulkheadExecutionQueueSemaphore(String applicationName, Object invocationTarget, - Method annotatedMethod, int bulkheadWaitingTaskQueue) { - BulkheadSemaphore bulkheadExecutionQueueSemaphore; - String fullMethodSignature = getFullMethodSignature(annotatedMethod); - - Map annotatedMethodExecutionQueueSemaphores = - stateByApplication.get(applicationName).getBulkheadExecutionQueueSemaphores().get(invocationTarget); - - // If there isn't a semaphore registered for this application name, register one, otherwise just return - // the one already registered - if (annotatedMethodExecutionQueueSemaphores == null) { - logger.log(Level.FINER, "No matching application in the bulkhead execution semaphore map, registering..."); - bulkheadExecutionQueueSemaphore = createBulkheadExecutionQueueSemaphore(applicationName, invocationTarget, - fullMethodSignature, bulkheadWaitingTaskQueue); - } else { - bulkheadExecutionQueueSemaphore = annotatedMethodExecutionQueueSemaphores.get(fullMethodSignature); - - // If there isn't a semaphore registered for this method signature, register one, otherwise just return - // the one already registered - if (bulkheadExecutionQueueSemaphore == null) { - logger.log(Level.FINER, "No matching method signature in the bulkhead execution queue semaphore map, " - + "registering..."); - bulkheadExecutionQueueSemaphore = createBulkheadExecutionQueueSemaphore(applicationName, invocationTarget, - fullMethodSignature, bulkheadWaitingTaskQueue); - } - } - - return bulkheadExecutionQueueSemaphore; + private BulkheadSemaphore getBulkheadExecutionSemaphore(String applicationName, Object invocationTarget, + Method annotatedMethod, int bulkheadValue) { + return getApplicationState(applicationName).getBulkheadExecutionSemaphores() + .computeIfAbsent(invocationTarget, key -> new ConcurrentHashMap<>()) + .computeIfAbsent( getFullMethodSignature(annotatedMethod), key -> new BulkheadSemaphore(bulkheadValue)); } - /** - * Helper method to create and register a Bulkhead Execution Queue Semaphore for an annotated method - * @param applicationName The name of the application - * @param invocationTarget The target object obtained from InvocationContext.getTarget() - * @param fullMethodSignature The method signature to register the semaphore against - * @param bulkheadWaitingTaskQueue The size of the waiting task queue of the bulkhead - * @return The Bulkhead Execution Queue Semaphore for the given method signature and application - */ - private synchronized BulkheadSemaphore createBulkheadExecutionQueueSemaphore(String applicationName, Object invocationTarget, - String fullMethodSignature, int bulkheadWaitingTaskQueue) { - // Double lock as multiple methods can get inside the calling if at the same time - logger.log(Level.FINER, "Checking double lock to see if something else has already added the object to " - + "the bulkhead execution queue semaphore map"); - FaultToleranceApplicationState applicationState = stateByApplication.get(applicationName); - Map> applicationSemaphores = applicationState.getBulkheadExecutionQueueSemaphores(); - if (applicationSemaphores.get(invocationTarget) == null) { - logger.log(Level.FINER, "Registering object to the bulkhead execution queue semaphore map: {0}", - invocationTarget); - applicationSemaphores.put(invocationTarget, new ConcurrentHashMap<>()); - } - - // Double lock as multiple methods can get inside the calling if at the same time - logger.log(Level.FINER, "Checking double lock to see if something else has already added the annotated method " - + "to the bulkhead execution queue semaphore map"); - if (applicationSemaphores.get(invocationTarget).get(fullMethodSignature) == null) { - logger.log(Level.FINER, "Registering semaphore for method {0} to the bulkhead execution semaphore map", - fullMethodSignature); - applicationSemaphores.get(invocationTarget).put(fullMethodSignature, - new BulkheadSemaphore(bulkheadWaitingTaskQueue)); - } - - return applicationSemaphores.get(invocationTarget).get(fullMethodSignature); + private BulkheadSemaphore getBulkheadExecutionQueueSemaphore(String applicationName, Object invocationTarget, + Method annotatedMethod, int bulkheadWaitingTaskQueue) { + return getApplicationState(applicationName).getBulkheadExecutionQueueSemaphores() + .computeIfAbsent(invocationTarget, key -> new ConcurrentHashMap<>()) + .computeIfAbsent( getFullMethodSignature(annotatedMethod), key -> new BulkheadSemaphore(bulkheadWaitingTaskQueue)); } - /** - * Gets the CircuitBreakerState object for a given application name and method.If a CircuitBreakerState hasn't been - * registered for the given application name and method, it will register the given CircuitBreaker. - * @param applicationName The name of the application - * @param invocationTarget The target object obtained from InvocationContext.getTarget() - * @param annotatedMethod The method annotated with @CircuitBreaker - * @param circuitBreaker The @CircuitBreaker annotation from the annotated method - * @return The CircuitBreakerState for the given application and method - */ private CircuitBreakerState getCircuitBreakerState(String applicationName, Object invocationTarget, Method annotatedMethod, int requestVolumeThreshold) { - CircuitBreakerState circuitBreakerState; - String fullMethodSignature = getFullMethodSignature(annotatedMethod); - - Map annotatedMethodCircuitBreakerStates = - stateByApplication.get(applicationName).getCircuitBreakerStates().get(invocationTarget); - - // If there isn't a CircuitBreakerState registered for this application name, register one, otherwise just - // return the one already registered - if (annotatedMethodCircuitBreakerStates == null) { - logger.log(Level.FINER, "No matching object in the circuit breaker states map, registering..."); - circuitBreakerState = registerCircuitBreaker(applicationName, invocationTarget, fullMethodSignature, - requestVolumeThreshold); - } else { - circuitBreakerState = annotatedMethodCircuitBreakerStates.get(fullMethodSignature); - - // If there isn't a CircuitBreakerState registered for this method, register one, otherwise just - // return the one already registered - if (circuitBreakerState == null) { - logger.log(Level.FINER, "No matching method in the circuit breaker states map, registering..."); - circuitBreakerState = registerCircuitBreaker(applicationName, invocationTarget, fullMethodSignature, - requestVolumeThreshold); - } - } - - return circuitBreakerState; - } - - /** - * Helper method to create and register a CircuitBreakerState object for an annotated method - * @param applicationName The application name to register the CircuitBreakerState against - * @param fullMethodSignature The method signature to register the CircuitBreakerState against - * @param bulkheadWaitingTaskQueue The CircuitBreaker annotation of the annotated method - * @return The CircuitBreakerState object for the given method signature and application - */ - private synchronized CircuitBreakerState registerCircuitBreaker(String applicationName, Object invocationTarget, - String fullMethodSignature, int requestVolumeThreshold) { - // Double lock as multiple methods can get inside the calling if at the same time - logger.log(Level.FINER, "Checking double lock to see if something else has already added the object " - + "to the circuit breaker states map"); - Map> applicationStates = stateByApplication.get(applicationName).getCircuitBreakerStates(); - Map targetStates = applicationStates.get(invocationTarget); - if (targetStates == null) { - logger.log(Level.FINER, "Registering application to the circuit breaker states map: {0}", - invocationTarget); - applicationStates.put(invocationTarget, new ConcurrentHashMap<>()); - } - - // Double lock as multiple methods can get inside the calling if at the same time - logger.log(Level.FINER, "Checking double lock to see if something else has already added the annotated method " - + "to the circuit breaker states map"); - if (targetStates.get(fullMethodSignature) == null) { - logger.log(Level.FINER, "Registering CircuitBreakerState for method {0} to the circuit breaker states map", - fullMethodSignature); - targetStates - .put(fullMethodSignature, new CircuitBreakerState(requestVolumeThreshold)); - } - - return targetStates.get(fullMethodSignature); + return getApplicationState(applicationName).getCircuitBreakerStates() + .computeIfAbsent(invocationTarget, key -> new ConcurrentHashMap<>()) + .computeIfAbsent(getFullMethodSignature(annotatedMethod), key -> new CircuitBreakerState(requestVolumeThreshold)); } /** @@ -504,22 +271,6 @@ public CircuitBreakerState getState(int requestVolumeThreshold, InvocationContex context.getMethod(), requestVolumeThreshold); } - /** - * Helper method that schedules the CircuitBreaker state to be set to HalfOpen after the configured delay - * @param delayMillis The number of milliseconds to wait before setting the state - * @param circuitBreakerState The CircuitBreakerState to set the state of - * @throws NamingException If the ManagedScheduledExecutor couldn't be found - */ - @Override - public void scheduleHalfOpen(long delayMillis, CircuitBreakerState circuitBreakerState) throws NamingException { - Runnable halfOpen = () -> { - circuitBreakerState.setCircuitState(CircuitBreakerState.CircuitState.HALF_OPEN); - logger.log(Level.FINE, "Setting CircuitBreaker state to half open"); - }; - getManagedScheduledExecutorService().schedule(halfOpen, delayMillis, TimeUnit.MILLISECONDS); - logger.log(Level.FINER, "CircuitBreaker half open state scheduled in {0} milliseconds", delayMillis); - } - @Override public BulkheadSemaphore getExecutionSemaphoreOf(int maxConcurrentThreads, InvocationContext context) { return getBulkheadExecutionSemaphore(getApplicationName(context), @@ -533,14 +284,64 @@ public BulkheadSemaphore getWaitingQueueSemaphoreOf(int queueCapacity, Invocatio } @Override - public Future runAsynchronous(InvocationContext context) throws Exception { - return getManagedExecutorService().submit(() -> context.proceed()); + public void delay(long delayMillis, InvocationContext context) throws InterruptedException { + if (delayMillis <= 0) { + return; + } + startTrace("delayRetry", context); + try { + Thread.sleep(delayMillis); + } finally { + endTrace(); + } } @Override - public Future timeoutIn(long millis) throws Exception { - final Thread thread = Thread.currentThread(); - return getManagedScheduledExecutorService().schedule(thread::interrupt, millis, TimeUnit.MILLISECONDS); + public void runAsynchronous(CompletableFuture asyncResult, Callable operation) throws Exception { + Runnable task = () -> { + if (!asyncResult.isCancelled() && !Thread.currentThread().isInterrupted()) { + try { + Future futureResult = AsynchronousPolicy.toFuture(operation.call()); + if (!asyncResult.isCancelled()) { // could be cancelled in the meanwhile + if (!asyncResult.isDone()) { + asyncResult.complete(futureResult.get()); + } + } else { + futureResult.cancel(true); + } + } catch (ExecutionException ex) { + asyncResult.completeExceptionally(ex.getCause()); + } catch (Exception ex) { + asyncResult.completeExceptionally(ex); + } + } + }; + getManagedExecutorService().submit(task); + } + + @Override + public Future scheduleDelayed(long delayMillis, Runnable operation) throws Exception { + return getManagedScheduledExecutorService().schedule(operation, delayMillis, TimeUnit.MILLISECONDS); + } + + @Override + public Object fallbackHandle(Class> fallbackClass, InvocationContext context, + Exception exception) throws Exception { + return CDI.current().select(fallbackClass).get() + .handle(new FaultToleranceExecutionContext(context.getMethod(), context.getParameters(), exception)); + } + + @Override + public Object fallbackInvoke(String fallbackMethod, InvocationContext context) throws Exception { + try { + return FaultToleranceCdiUtils.getAnnotatedMethodClass(context, Fallback.class) + .getDeclaredMethod(fallbackMethod, context.getMethod().getParameterTypes()) + .invoke(context.getTarget(), context.getParameters()); + } catch (InvocationTargetException e) { + throw (Exception) e.getTargetException(); + } catch (IllegalAccessException | NoSuchMethodException e) { + throw new FaultToleranceDefinitionException(e); // should not happen as we validated + } } @Override diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/cdi/FaultToleranceCDIExtension.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/cdi/FaultToleranceCDIExtension.java index e16955bc3e4..2d53593560a 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/cdi/FaultToleranceCDIExtension.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/cdi/FaultToleranceCDIExtension.java @@ -39,8 +39,9 @@ */ package fish.payara.microprofile.faulttolerance.cdi; +import fish.payara.microprofile.faulttolerance.interceptors.FaultToleranceBehaviour; import fish.payara.microprofile.faulttolerance.interceptors.FaultToleranceInterceptor; -import fish.payara.microprofile.faulttolerance.model.FaultToleranceBehaviour; + import java.lang.annotation.Annotation; import java.lang.reflect.Method; @@ -75,7 +76,7 @@ public class FaultToleranceCDIExtension implements Extension { private static final Annotation MARKER = () -> FaultToleranceBehaviour.class; void beforeBeanDiscovery(@Observes BeforeBeanDiscovery beforeBeanDiscovery, BeanManager beanManager) { - beforeBeanDiscovery.addAnnotatedType(beanManager.createAnnotatedType(FaultToleranceInterceptor.class)); + beforeBeanDiscovery.addAnnotatedType(beanManager.createAnnotatedType(FaultToleranceInterceptor.class), "MP-FT"); } void processAnnotatedType(@Observes @WithAnnotations({ Asynchronous.class, Bulkhead.class, CircuitBreaker.class, diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/AsynchronousInterceptor.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/AsynchronousInterceptor.java index ec1067762e0..34a715b77de 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/AsynchronousInterceptor.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/AsynchronousInterceptor.java @@ -39,13 +39,9 @@ */ package fish.payara.microprofile.faulttolerance.interceptors; -import fish.payara.microprofile.faulttolerance.interceptors.fallback.FallbackPolicy; import java.io.Serializable; import java.util.concurrent.CompletionStage; -import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; import java.util.logging.Level; import javax.annotation.Priority; import javax.interceptor.AroundInvoke; @@ -55,7 +51,6 @@ import org.eclipse.microprofile.faulttolerance.Asynchronous; import org.eclipse.microprofile.faulttolerance.Fallback; -import org.eclipse.microprofile.faulttolerance.exceptions.FaultToleranceException; /** * Interceptor for the Fault Tolerance Asynchronous Annotation. Also contains the wrapper class for the Future outcome. @@ -94,8 +89,8 @@ public Object intercept(InvocationContext context) throws Exception { // propagate the exception upwards if (fallback != null && getConfig().isEnabled(Fallback.class, context)) { logger.log(Level.FINE, "Fallback annotation found on method - falling back from Asynchronous"); - FallbackPolicy fallbackPolicy = new FallbackPolicy(fallback, getConfig(), getExecution(), getMetrics(), context); - resultValue = fallbackPolicy.fallback(context, ex); + //FallbackPolicy fallbackPolicy = new FallbackPolicy(fallback, getConfig(), getExecution(), getMetrics(), context); + resultValue = null; // fallbackPolicy.fallback(context, ex); } else { throw ex; } @@ -113,76 +108,10 @@ private Object asynchronous(InvocationContext context) throws Exception, NamingE } if (returnType == Future.class) { logger.log(Level.FINER, "Proceeding invocation asynchronously"); - return new FutureDelegator(getExecution().runAsynchronous(context)); + return null; //TODO run } logger.log(Level.SEVERE, "Unsupported return type for @Asynchronous annotated method: " + returnType + ", proceeding normally without asynchronous."); return context.proceed(); } - - /** - * Wrapper class for the Future object - */ - static class FutureDelegator implements Future { - - private final Future future; - - public FutureDelegator(Future future) { - this.future = future; - } - - @Override - public boolean cancel(boolean mayInterruptIfRunning) { - return future.cancel(mayInterruptIfRunning); - } - - @Override - public boolean isCancelled() { - return future.isCancelled(); - } - - @Override - public boolean isDone() { - return future.isDone(); - } - - @Override - public Object get() throws InterruptedException, ExecutionException { - try { - Object resultValue = future.get(); - - // If the result of future.get() is still a future, get it again - if (resultValue instanceof Future) { - Future tempFuture = (Future) resultValue; - return tempFuture.get(); - } - return resultValue; - } catch (InterruptedException | ExecutionException ex) { - if (ex.getCause() instanceof FaultToleranceException) { - throw (FaultToleranceException) ex.getCause(); - } - throw ex; - } - } - - @Override - public Object get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { - try { - Object resultValue = future.get(timeout, unit); - - // If the result of future.get() is still a future, get it again - if (resultValue instanceof Future) { - Future tempFuture = (Future) resultValue; - return tempFuture.get(timeout, unit); - } - return resultValue; - } catch (InterruptedException | ExecutionException | TimeoutException ex) { - if (ex.getCause() instanceof FaultToleranceException) { - throw new ExecutionException(ex.getCause()); - } - throw ex; - } - } - - } } diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/BaseFaultToleranceInterceptor.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/BaseFaultToleranceInterceptor.java index 31f69cf061c..a1c452cc994 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/BaseFaultToleranceInterceptor.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/BaseFaultToleranceInterceptor.java @@ -4,7 +4,6 @@ import java.util.logging.Level; import java.util.logging.Logger; -import javax.inject.Inject; import javax.interceptor.AroundInvoke; import javax.interceptor.InvocationContext; diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/CircuitBreakerInterceptor.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/CircuitBreakerInterceptor.java index fa3c2bcce7b..e4ee8f8b09b 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/CircuitBreakerInterceptor.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/CircuitBreakerInterceptor.java @@ -132,9 +132,9 @@ private Object circuitBreak(InvocationContext context) throws Exception { CircuitBreakerState circuitBreakerState = getExecution().getState(requestVolumeThreshold, context); if (getConfig().isMetricsEnabled(context)) { - getMetrics().insertCircuitbreakerOpenTotal(circuitBreakerState::open, context); - getMetrics().insertCircuitbreakerHalfOpenTotal(circuitBreakerState::halfOpen, context); - getMetrics().insertCircuitbreakerClosedTotal(circuitBreakerState::closed, context); + getMetrics().insertCircuitbreakerOpenTotal(circuitBreakerState::isOpen, context); + getMetrics().insertCircuitbreakerHalfOpenTotal(circuitBreakerState::isHalfOpen, context); + getMetrics().insertCircuitbreakerClosedTotal(circuitBreakerState::isClosed, context); } switch (circuitBreakerState.getCircuitState()) { @@ -198,7 +198,7 @@ private Object circuitBreak(InvocationContext context) throws Exception { // Open the circuit again, and reset the half-open result counter circuitBreakerState.setCircuitState(CircuitBreakerState.CircuitState.OPEN); circuitBreakerState.resetHalfOpenSuccessfulResultCounter(); - getExecution().scheduleHalfOpen(delayMillis, circuitBreakerState); + getExecution().scheduleDelayed(delayMillis, circuitBreakerState::halfOpen); } throw ex; @@ -281,7 +281,7 @@ private void breakCircuitIfRequired(long failureThreshold, CircuitBreakerState c getMetrics().incrementCircuitbreakerOpenedTotal(context); // Kick off a thread that will half-open the circuit after the specified delay - getExecution().scheduleHalfOpen(delayMillis, circuitBreakerState); + getExecution().scheduleDelayed(delayMillis, circuitBreakerState::halfOpen); } } diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/model/FaultToleranceBehaviour.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/FaultToleranceBehaviour.java similarity index 94% rename from appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/model/FaultToleranceBehaviour.java rename to appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/FaultToleranceBehaviour.java index 6cb4b3adab9..6591f5d86a2 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/model/FaultToleranceBehaviour.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/FaultToleranceBehaviour.java @@ -1,4 +1,4 @@ -package fish.payara.microprofile.faulttolerance.model; +package fish.payara.microprofile.faulttolerance.interceptors; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/FaultToleranceInterceptor.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/FaultToleranceInterceptor.java index 0f7b4fde333..5970448bb62 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/FaultToleranceInterceptor.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/FaultToleranceInterceptor.java @@ -1,7 +1,10 @@ package fish.payara.microprofile.faulttolerance.interceptors; +import java.io.Serializable; import java.lang.annotation.Annotation; import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; import javax.annotation.Priority; import javax.enterprise.inject.spi.BeanManager; @@ -10,19 +13,23 @@ import javax.interceptor.Interceptor; import javax.interceptor.InvocationContext; +import org.eclipse.microprofile.faulttolerance.exceptions.FaultToleranceDefinitionException; import org.glassfish.internal.api.Globals; import fish.payara.microprofile.faulttolerance.FaultToleranceConfig; import fish.payara.microprofile.faulttolerance.FaultToleranceExecution; +import fish.payara.microprofile.faulttolerance.FaultToleranceMetrics; import fish.payara.microprofile.faulttolerance.cdi.CdiFaultToleranceConfig; +import fish.payara.microprofile.faulttolerance.cdi.CdiFaultToleranceMetrics; import fish.payara.microprofile.faulttolerance.cdi.FaultToleranceCdiUtils.Stereotypes; -import fish.payara.microprofile.faulttolerance.model.FaultToleranceBehaviour; -import fish.payara.microprofile.faulttolerance.model.FaultTolerancePolicy; +import fish.payara.microprofile.faulttolerance.policy.FaultTolerancePolicy; @Interceptor @FaultToleranceBehaviour @Priority(Interceptor.Priority.PLATFORM_AFTER) -public class FaultToleranceInterceptor implements Stereotypes { +public class FaultToleranceInterceptor implements Stereotypes, Serializable { + + private static final Logger logger = Logger.getLogger(FaultToleranceInterceptor.class.getName()); @Inject private BeanManager beanManager; @@ -30,14 +37,17 @@ public class FaultToleranceInterceptor implements Stereotypes { @AroundInvoke public Object intercept(InvocationContext context) throws Exception { try { - FaultToleranceExecution execution = - Globals.getDefaultBaseServiceLocator().getService(FaultToleranceExecution.class); - FaultTolerancePolicy info = FaultTolerancePolicy.get(context, this::getConfig); - if (info.isFaultToleranceEnabled) { - System.out.println("yes"); + FaultTolerancePolicy policy = FaultTolerancePolicy.get(context, this::getConfig); + if (policy.isFaultToleranceEnabled) { + FaultToleranceExecution execution = + Globals.getDefaultBaseServiceLocator().getService(FaultToleranceExecution.class); + FaultToleranceMetrics metrics = policy.isMetricsEnabled ? new CdiFaultToleranceMetrics(null) : null; + return policy.processWithFaultTolerance(context, execution, metrics); } - } catch (Exception e) { - e.printStackTrace(); + } catch (FaultToleranceDefinitionException e) { + logger.log(Level.SEVERE, "Effective FT policy contains illegal values, fault tolerance cannot be applied," + + " falling back to plain method invocation.", e); + // fall-through to normal proceed } return context.proceed(); } diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/TimeoutInterceptor.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/TimeoutInterceptor.java index f0012967cd5..c289488ee4b 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/TimeoutInterceptor.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/TimeoutInterceptor.java @@ -135,7 +135,7 @@ private Object timeout(InvocationContext context) throws Exception { long executionStartTime = System.nanoTime(); try { - timeoutFuture = getExecution().timeoutIn(timeoutMillis); + timeoutFuture = getExecution().scheduleDelayed(timeoutMillis, Thread.currentThread()::interrupt); resultValue = context.proceed(); stopTimeout(timeoutFuture); diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/fallback/FallbackPolicy.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/fallback/FallbackPolicy.java index 2f6926fd879..ac3b8504a69 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/fallback/FallbackPolicy.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/fallback/FallbackPolicy.java @@ -41,9 +41,10 @@ import fish.payara.microprofile.faulttolerance.FaultToleranceConfig; import fish.payara.microprofile.faulttolerance.FaultToleranceExecution; +import fish.payara.microprofile.faulttolerance.FaultToleranceExecutionContext; import fish.payara.microprofile.faulttolerance.FaultToleranceMetrics; import fish.payara.microprofile.faulttolerance.cdi.FaultToleranceCdiUtils; -import java.lang.reflect.Method; + import java.util.logging.Level; import java.util.logging.Logger; import javax.interceptor.InvocationContext; @@ -109,35 +110,4 @@ public Object fallback(InvocationContext context, Throwable exception) throws Ex } return resultValue; } - - /** - * Default implementation class for the Fault Tolerance ExecutionContext interface - */ - private class FaultToleranceExecutionContext implements ExecutionContext { - - private final Method method; - private final Object[] parameters; - private final Throwable failure; - - public FaultToleranceExecutionContext(Method method, Object[] parameters, Throwable failure) { - this.method = method; - this.parameters = parameters; - this.failure = failure; - } - - @Override - public Method getMethod() { - return method; - } - - @Override - public Object[] getParameters() { - return parameters; - } - - @Override - public Throwable getFailure() { - return failure; - } - } } diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/model/FaultTolerancePolicy.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/model/FaultTolerancePolicy.java deleted file mode 100644 index 63e0d9c5813..00000000000 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/model/FaultTolerancePolicy.java +++ /dev/null @@ -1,122 +0,0 @@ -package fish.payara.microprofile.faulttolerance.model; - -import java.io.Serializable; -import java.lang.reflect.Method; -import java.util.concurrent.ConcurrentHashMap; -import java.util.function.Supplier; - -import javax.interceptor.InvocationContext; - -import org.eclipse.microprofile.faulttolerance.exceptions.FaultToleranceDefinitionException; - -import fish.payara.microprofile.faulttolerance.FaultToleranceConfig; - -/** - * The {@link FaultTolerancePolicy} describes the effective aggregated policies to use for a particular {@link Method} - * when adding fault tolerant behaviour to it. - * - * The policies are extracted from FT annotations and the {@link FaultToleranceConfig}. - * - * In contrast to the plain annotations the policies do consider configuration overrides and include validation of the - * effective values. - * - * The policy class also reduces the need to analyse FT annotations for each invocation and works as a consistent source - * of truth throughout the execution of FT behaviour that is convenient to pass around as a single immutable value. - * - * @author Jan Bernitt - */ -public final class FaultTolerancePolicy implements Serializable { - - private static final long TTL = 60 * 1000; - - private static final ConcurrentHashMap POLICY_BY_METHOD = new ConcurrentHashMap<>(); - - public static void clean() { - long now = System.currentTimeMillis(); - POLICY_BY_METHOD.entrySet().removeIf(entry -> now > entry.getValue().expiresMillis); - } - - /** - * Returns the {@link FaultTolerancePolicy} to use for the method invoked in the current context. - * - * @param context current context - * @param configSpplier supplies the configuration (if needed, in case returned policy needs to be created with help - * of the {@link FaultToleranceConfig}) - * @return the policy to apply - * @throws FaultToleranceDefinitionException in case the effective policy contains illegal values - */ - public static FaultTolerancePolicy get(InvocationContext context, Supplier configSpplier) - throws FaultToleranceDefinitionException { - return POLICY_BY_METHOD.compute(context.getMethod(), (method, info) -> - info != null && !info.isExpired() ? info : create(context, configSpplier)); - } - - private static FaultTolerancePolicy create(InvocationContext context, Supplier configSpplier) { - FaultToleranceConfig config = configSpplier.get(); - boolean isFaultToleranceEnabled = config.isEnabled(context); - if (!isFaultToleranceEnabled) { - return new FaultTolerancePolicy(false, false, null, null, null, null, null, null); - } - return new FaultTolerancePolicy(isFaultToleranceEnabled, - config.isMetricsEnabled(context), - AsynchronousPolicy.create(context, config), - BulkheadPolicy.create(context, config), - CircuitBreakerPolicy.create(context, config), - FallbackPolicy.create(context, config), - RetryPolicy.create(context, config), - TimeoutPolicy.create(context, config)); - } - - private final long expiresMillis; - public final boolean isFaultToleranceEnabled; - public final boolean isMetricsEnabled; - public final AsynchronousPolicy asynchronous; - public final BulkheadPolicy bulkhead; - public final CircuitBreakerPolicy circuitBreaker; - public final FallbackPolicy fallback; - public final RetryPolicy retry; - public final TimeoutPolicy timeout; - - public FaultTolerancePolicy(boolean isFaultToleranceEnabled, boolean isMetricsEnabled, AsynchronousPolicy asynchronous, - BulkheadPolicy bulkhead, CircuitBreakerPolicy circuitBreaker, FallbackPolicy fallback, RetryPolicy retry, - TimeoutPolicy timeout) { - this.expiresMillis = System.currentTimeMillis() + TTL; - this.isFaultToleranceEnabled = isFaultToleranceEnabled; - this.isMetricsEnabled = isMetricsEnabled; - this.asynchronous = asynchronous; - this.bulkhead = bulkhead; - this.circuitBreaker = circuitBreaker; - this.fallback = fallback; - this.retry = retry; - this.timeout = timeout; - } - - private boolean isExpired() { - return System.currentTimeMillis() > expiresMillis; - } - - public boolean isAsynchronous() { - return asynchronous != null; - } - - public boolean isBulkheadPresent() { - return bulkhead != null; - } - - public boolean isCircuitBreakerPresent() { - return circuitBreaker != null; - } - - public boolean isFallbackPresent() { - return fallback != null; - } - - public boolean isRetryPresent() { - return retry != null; - } - - public boolean isTimeoutPresent() { - return timeout != null; - } - -} diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/model/AsynchronousPolicy.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/AsynchronousPolicy.java similarity index 83% rename from appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/model/AsynchronousPolicy.java rename to appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/AsynchronousPolicy.java index 07f0385263e..87096094e9d 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/model/AsynchronousPolicy.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/AsynchronousPolicy.java @@ -1,4 +1,4 @@ -package fish.payara.microprofile.faulttolerance.model; +package fish.payara.microprofile.faulttolerance.policy; import java.util.concurrent.CompletionStage; import java.util.concurrent.Future; @@ -36,4 +36,10 @@ private static void checkReturnsFutureOrCompletionStage(InvocationContext contex + "does not return a Future or CompletionStage but: " + returnType.getName()); } } + + public static Future toFuture(Object asyncResult) { + return asyncResult instanceof CompletionStage + ? ((CompletionStage) asyncResult).toCompletableFuture() + : (Future) asyncResult; + } } diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/model/BulkheadPolicy.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/BulkheadPolicy.java similarity index 95% rename from appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/model/BulkheadPolicy.java rename to appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/BulkheadPolicy.java index 0fb56e3150d..92ff153912e 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/model/BulkheadPolicy.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/BulkheadPolicy.java @@ -1,4 +1,4 @@ -package fish.payara.microprofile.faulttolerance.model; +package fish.payara.microprofile.faulttolerance.policy; import java.lang.reflect.Method; diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/model/CircuitBreakerPolicy.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/CircuitBreakerPolicy.java similarity index 81% rename from appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/model/CircuitBreakerPolicy.java rename to appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/CircuitBreakerPolicy.java index a5d141c1ef9..c76897a996a 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/model/CircuitBreakerPolicy.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/CircuitBreakerPolicy.java @@ -1,7 +1,8 @@ -package fish.payara.microprofile.faulttolerance.model; +package fish.payara.microprofile.faulttolerance.policy; import java.lang.reflect.Method; import java.time.temporal.ChronoUnit; +import java.util.logging.Logger; import javax.interceptor.InvocationContext; @@ -14,6 +15,8 @@ */ public final class CircuitBreakerPolicy extends Policy { + static final Logger logger = Logger.getLogger(CircuitBreakerPolicy.class.getName()); + public final Class[] failOn; public final long delay; public final ChronoUnit delayUnit; @@ -49,4 +52,14 @@ public static CircuitBreakerPolicy create(InvocationContext context, FaultTolera } return null; } + + /** + * Helper method that checks whether or not the given exception is included in the failOn parameter. + * + * @param ex The exception to check + * @return True if the exception is covered by {@link #failOn} list of this policy + */ + public boolean failOn(Exception ex) { + return Policy.isCaught(ex, failOn); + } } diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/model/FallbackPolicy.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/FallbackPolicy.java similarity index 79% rename from appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/model/FallbackPolicy.java rename to appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/FallbackPolicy.java index a515eded3d2..7bb937f5242 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/model/FallbackPolicy.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/FallbackPolicy.java @@ -1,4 +1,4 @@ -package fish.payara.microprofile.faulttolerance.model; +package fish.payara.microprofile.faulttolerance.policy; import java.lang.reflect.Method; @@ -18,17 +18,25 @@ public final class FallbackPolicy extends Policy { public final Class> value; public final String fallbackMethod; + public final Method method; public FallbackPolicy(Method method, Class> value, String fallbackMethod) { checkUnambiguous(value, fallbackMethod); if (fallbackMethod != null && !fallbackMethod.isEmpty()) { checkReturnsSameAs(method, Fallback.class, "fallbackMethod", method.getDeclaringClass(), fallbackMethod, method.getParameterTypes()); + try { + this.method = method.getDeclaringClass().getDeclaredMethod(fallbackMethod, method.getParameterTypes()); + } catch (NoSuchMethodException | SecurityException e) { + throw new FaultToleranceDefinitionException(e); + } + } else { + this.method = null; } - if (value != null && value != Fallback.DEFAULT.class) { + this.value = value; + if (isHandlerPresent()) { checkReturnsSameAs(method, Fallback.class, "value", value, "handle", ExecutionContext.class); } - this.value = value; this.fallbackMethod = fallbackMethod; } @@ -47,4 +55,8 @@ private static void checkUnambiguous(Class> value, throw new FaultToleranceDefinitionException("Both a fallback class and method have been set."); } } + + public boolean isHandlerPresent() { + return value != null && value != Fallback.DEFAULT.class; + } } diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/FaultTolerancePolicy.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/FaultTolerancePolicy.java new file mode 100644 index 00000000000..f556f59fda1 --- /dev/null +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/FaultTolerancePolicy.java @@ -0,0 +1,401 @@ +package fish.payara.microprofile.faulttolerance.policy; + +import java.io.Serializable; +import java.lang.reflect.Method; +import java.time.Duration; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Supplier; +import java.util.logging.Logger; + +import javax.interceptor.InvocationContext; + +import org.eclipse.microprofile.faulttolerance.exceptions.BulkheadException; +import org.eclipse.microprofile.faulttolerance.exceptions.CircuitBreakerOpenException; +import org.eclipse.microprofile.faulttolerance.exceptions.FaultToleranceDefinitionException; +import org.eclipse.microprofile.faulttolerance.exceptions.FaultToleranceException; +import org.eclipse.microprofile.faulttolerance.exceptions.TimeoutException; + +import fish.payara.microprofile.faulttolerance.FaultToleranceConfig; +import fish.payara.microprofile.faulttolerance.FaultToleranceExecution; +import fish.payara.microprofile.faulttolerance.FaultToleranceMetrics; +import fish.payara.microprofile.faulttolerance.state.BulkheadSemaphore; +import fish.payara.microprofile.faulttolerance.state.CircuitBreakerState; + +/** + * The {@link FaultTolerancePolicy} describes the effective aggregated policies to use for a particular {@link Method} + * when adding fault tolerant behaviour to it. + * + * The policies are extracted from FT annotations and the {@link FaultToleranceConfig}. + * + * In contrast to the plain annotations the policies do consider configuration overrides and include validation of the + * effective values. + * + * The policy class also reduces the need to analyse FT annotations for each invocation and works as a consistent source + * of truth throughout the execution of FT behaviour that is convenient to pass around as a single immutable value. + * + * @author Jan Bernitt + */ +public final class FaultTolerancePolicy implements Serializable { + + private static final Logger logger = Logger.getLogger(FaultTolerancePolicy.class.getName()); + + private static final long TTL = 60 * 1000; + + private static final ConcurrentHashMap POLICY_BY_METHOD = new ConcurrentHashMap<>(); + + public static void clean() { + long now = System.currentTimeMillis(); + POLICY_BY_METHOD.entrySet().removeIf(entry -> now > entry.getValue().expiresMillis); + } + + /** + * Returns the {@link FaultTolerancePolicy} to use for the method invoked in the current context. + * + * @param context current context + * @param configSpplier supplies the configuration (if needed, in case returned policy needs to be created with help + * of the {@link FaultToleranceConfig}) + * @return the policy to apply + * @throws FaultToleranceDefinitionException in case the effective policy contains illegal values + */ + public static FaultTolerancePolicy get(InvocationContext context, Supplier configSpplier) + throws FaultToleranceDefinitionException { + return POLICY_BY_METHOD.compute(context.getMethod(), (method, info) -> + info != null && !info.isExpired() ? info : create(context, configSpplier)); + } + + private static FaultTolerancePolicy create(InvocationContext context, Supplier configSpplier) { + FaultToleranceConfig config = configSpplier.get(); + boolean isFaultToleranceEnabled = config.isEnabled(context); + if (!isFaultToleranceEnabled) { + return new FaultTolerancePolicy(false, false, null, null, null, null, null, null); + } + return new FaultTolerancePolicy(isFaultToleranceEnabled, + config.isMetricsEnabled(context), + AsynchronousPolicy.create(context, config), + BulkheadPolicy.create(context, config), + CircuitBreakerPolicy.create(context, config), + FallbackPolicy.create(context, config), + RetryPolicy.create(context, config), + TimeoutPolicy.create(context, config)); + } + + private final long expiresMillis; + public final boolean isFaultToleranceEnabled; + public final boolean isMetricsEnabled; + public final AsynchronousPolicy asynchronous; + public final BulkheadPolicy bulkhead; + public final CircuitBreakerPolicy circuitBreaker; + public final FallbackPolicy fallback; + public final RetryPolicy retry; + public final TimeoutPolicy timeout; + + public FaultTolerancePolicy(boolean isFaultToleranceEnabled, boolean isMetricsEnabled, AsynchronousPolicy asynchronous, + BulkheadPolicy bulkhead, CircuitBreakerPolicy circuitBreaker, FallbackPolicy fallback, RetryPolicy retry, + TimeoutPolicy timeout) { + this.expiresMillis = System.currentTimeMillis() + TTL; + this.isFaultToleranceEnabled = isFaultToleranceEnabled; + this.isMetricsEnabled = isMetricsEnabled; + this.asynchronous = asynchronous; + this.bulkhead = bulkhead; + this.circuitBreaker = circuitBreaker; + this.fallback = fallback; + this.retry = retry; + this.timeout = timeout; + } + + private boolean isExpired() { + return System.currentTimeMillis() > expiresMillis; + } + + public boolean isAsynchronous() { + return asynchronous != null; + } + + public boolean isBulkheadPresent() { + return bulkhead != null; + } + + public boolean isCircuitBreakerPresent() { + return circuitBreaker != null; + } + + public boolean isFallbackPresent() { + return fallback != null; + } + + public boolean isRetryPresent() { + return retry != null; + } + + public boolean isTimeoutPresent() { + return timeout != null; + } + + interface InvocationStage { + Object run(FaultToleranceInvocation invocation) throws Exception; + } + + static final class FaultToleranceInvocation { + final InvocationContext context; + final FaultToleranceExecution execution; + final FaultToleranceMetrics metrics; + final CompletableFuture asyncResult; + final Set asyncWorkers; + + FaultToleranceInvocation(InvocationContext context, FaultToleranceExecution execution, FaultToleranceMetrics metrics, + CompletableFuture asyncResult, Set asyncWorkers) { + this.context = context; + this.execution = execution; + this.metrics = metrics; + this.asyncResult = asyncResult; + this.asyncWorkers = asyncWorkers; + } + + Object runStageWithWorker(InvocationStage stage) throws Exception { + Thread current = Thread.currentThread(); + asyncWorkers.add(current); + try { + return stage.run(this); + } finally { + asyncWorkers.remove(current); + } + } + } + + public Object processWithFaultTolerance(InvocationContext context, FaultToleranceExecution execution, + FaultToleranceMetrics metrics) throws Exception { + if (!isFaultToleranceEnabled) { + return context.proceed(); + } + return processAsynchronousStage(context, execution, metrics); + } + + /** + * Stage that takes care of the {@link AsynchronousPolicy} handling. + */ + private Object processAsynchronousStage(InvocationContext context, FaultToleranceExecution execution, + FaultToleranceMetrics metrics) throws Exception { + if (!isAsynchronous()) { + return processFallbackStage(new FaultToleranceInvocation(context, execution, metrics, null, null)); + } + Set asyncWorkers = ConcurrentHashMap.newKeySet(); + CompletableFuture asyncResult = new CompletableFuture() { + + @Override + public boolean cancel(boolean mayInterruptIfRunning) { + if (super.cancel(mayInterruptIfRunning)) { + if (mayInterruptIfRunning) { + asyncWorkers.forEach(worker -> worker.interrupt()); + } + return true; + } + return false; + } + }; + FaultToleranceInvocation invocation = new FaultToleranceInvocation(context, execution, metrics, asyncResult, + asyncWorkers); + execution.runAsynchronous(asyncResult, () -> invocation.runStageWithWorker(this::processFallbackStage)); + return asyncResult; + } + + /** + * Stage that takes care of the {@link FallbackPolicy} handling. + */ + private Object processFallbackStage(FaultToleranceInvocation invocation) throws Exception { + if (!isFallbackPresent()) { + return processRetryStage(invocation); + } + try { + return processRetryStage(invocation); + } catch (Exception ex) { + if (fallback.isHandlerPresent()) { + return invocation.execution.fallbackHandle(fallback.value, invocation.context, ex); + } + return invocation.execution.fallbackInvoke(fallback.fallbackMethod, invocation.context); + } + } + + /** + * Stage that takes care of the {@link RetryPolicy} handling. + */ + private Object processRetryStage(FaultToleranceInvocation invocation) throws Exception { + int attemptsLeft = retry.totalAttempts(); + Long retryTimeoutTime = retry.timeoutTimeNow(); + while (attemptsLeft > 0) { + attemptsLeft--; + try { + return isAsynchronous() ? processRetryAsync(invocation) : processCircuitBreakerStage(invocation, null); + } catch (Exception ex) { + if (attemptsLeft <= 0 + || !retry.retryOn(ex) + || retryTimeoutTime != null && System.currentTimeMillis() >= retryTimeoutTime) { + throw ex; + } + if (retry.isDelayed()) { + invocation.execution.delay(retry.jitteredDelay(), invocation.context); + } + } + logger.info("Retry: " + attemptsLeft); + } + // this line should never be reached as we throw above + throw new FaultToleranceException("Retry failed"); + } + + private Object processRetryAsync(FaultToleranceInvocation invocation) throws Exception { + CompletableFuture asyncTry = new CompletableFuture<>(); + invocation.execution.runAsynchronous(asyncTry, + () -> invocation.runStageWithWorker(inv -> processCircuitBreakerStage(inv, asyncTry))); + try { + asyncTry.get(); // wait and only proceed on success + if (invocation.asyncResult.isDone()) { // maybe another try finished by now? + throw new TimeoutException(); + } + return asyncTry; + } catch (ExecutionException ex) { + throw (Exception) ex.getCause(); + } + } + + /** + * Stage that takes care of the {@link CircuitBreakerPolicy} handling. + */ + private Object processCircuitBreakerStage(FaultToleranceInvocation invocation, CompletableFuture asyncTry) throws Exception { + if (!isCircuitBreakerPresent()) { + return processTimeoutStage(invocation, asyncTry); + } + CircuitBreakerState state = invocation.execution.getState(circuitBreaker.requestVolumeThreshold, invocation.context); + Object resultValue = null; + switch (state.getCircuitState()) { + default: + case OPEN: + throw new CircuitBreakerOpenException(); + case HALF_OPEN: + try { + resultValue = processTimeoutStage(invocation, asyncTry); + } catch (Exception ex) { + if (circuitBreaker.failOn(ex)) { + state.open(); + invocation.execution.scheduleDelayed(circuitBreaker.delay, state::halfOpen); + } + throw ex; + } + state.halfOpenSuccessful(circuitBreaker.successThreshold); + return resultValue; + case CLOSED: + Exception failedOn = null; + try { + resultValue = processTimeoutStage(invocation, asyncTry); + state.recordClosedResult(true); + } catch (Exception ex) { + if (circuitBreaker.failOn(ex)) { + state.recordClosedResult(false); + } + failedOn = ex; + } + if (state.isOverFailureThreshold(Math.round(circuitBreaker.requestVolumeThreshold * circuitBreaker.failureRatio))) { + state.open(); + invocation.execution.scheduleDelayed(circuitBreaker.delay, state::halfOpen); + } + if (failedOn != null) { + throw failedOn; + } + return resultValue; + } + } + + /** + * Stage that takes care of the {@link TimeoutPolicy} handling. + */ + private Object processTimeoutStage(FaultToleranceInvocation invocation, CompletableFuture asyncTry) throws Exception { + if (!isTimeoutPresent()) { + return processBulkheadStage(invocation); + } + long timeoutDuration = Duration.of(timeout.value, timeout.unit).toMillis(); + long timeoutTime = System.currentTimeMillis() + timeoutDuration; + Thread current = Thread.currentThread(); + AtomicBoolean didTimeout = new AtomicBoolean(false); + Future timeout = invocation.execution.scheduleDelayed(timeoutDuration, () -> { + didTimeout.set(true); + current.interrupt(); + if (asyncTry != null) { + // we do this since interrupting not necessarily returns directly or ever but the attempt should timeout now + asyncTry.completeExceptionally(new TimeoutException()); + } + }); + try { + Object resultValue = processBulkheadStage(invocation); + if (current.isInterrupted()) { + Thread.interrupted(); // clear the flag + } + if (didTimeout.get() || System.currentTimeMillis() > timeoutTime) { + logger.info("throwing TimeoutException"); + throw new TimeoutException(); + } + logger.info("returning "+resultValue); + return resultValue; + } catch (Exception ex) { + if ((ex instanceof InterruptedException || ex.getCause() instanceof InterruptedException) + && System.currentTimeMillis() > timeoutTime) { + logger.info("throwing TimeoutException after interrupted"); + throw new TimeoutException(ex); + } + logger.info("throwing a "+ex.getClass().getSimpleName()); + throw ex; + } finally { + timeout.cancel(true); + } + } + + /** + * Stage that takes care of the {@link BulkheadPolicy} handling. + */ + private Object processBulkheadStage(FaultToleranceInvocation invocation) throws Exception { + if (!isBulkheadPresent()) { + return processMethodCallStage(invocation); + } + BulkheadSemaphore executionSemaphore = invocation.execution.getExecutionSemaphoreOf(bulkhead.value, invocation.context); + if (executionSemaphore.tryAcquire(0, TimeUnit.SECONDS)) { + try { + return processMethodCallStage(invocation); + } finally { + executionSemaphore.release(); + } + } + if (!isAsynchronous()) { // plain semaphore style, fail: + throw new BulkheadException("No free work permits."); + } + // from here: queueing style: + BulkheadSemaphore waitingQueueSemaphore = invocation.execution.getWaitingQueueSemaphoreOf(bulkhead.waitingTaskQueue, invocation.context); + if (waitingQueueSemaphore.tryAcquire(0, TimeUnit.SECONDS)) { + try { + executionSemaphore.acquire(); // block until execution permit becomes available + } catch (InterruptedException ex) { + waitingQueueSemaphore.release(); + throw new BulkheadException(ex); + } + waitingQueueSemaphore.release(); + try { + return processMethodCallStage(invocation); + } finally { + executionSemaphore.release(); + } + } + throw new BulkheadException("No free work or queue permits."); + } + + /** + * Stage where the actual wrapped method call occurs. + */ + private Object processMethodCallStage(FaultToleranceInvocation invocation) throws Exception { + if (isAsynchronous() && invocation.asyncResult.isDone()) { + throw new TimeoutException(); + } + return invocation.context.proceed(); + } +} diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/model/Policy.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/Policy.java similarity index 77% rename from appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/model/Policy.java rename to appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/Policy.java index 59b95c3fb4c..b5fb3085763 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/model/Policy.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/Policy.java @@ -1,8 +1,9 @@ -package fish.payara.microprofile.faulttolerance.model; +package fish.payara.microprofile.faulttolerance.policy; import java.io.Serializable; import java.lang.annotation.Annotation; import java.lang.reflect.Method; +import java.util.logging.Level; import org.eclipse.microprofile.faulttolerance.exceptions.FaultToleranceDefinitionException; @@ -63,4 +64,25 @@ protected static String describe(Method annotatedMethod, Class[] caught) { + if (caught.length == 0) { + return false; + } + if (caught[0] == Throwable.class) { + return true; + } + for (Class caughtType : caught) { + if (ex.getClass() == caughtType) { + CircuitBreakerPolicy.logger.log(Level.FINER, "Exception {0} matches a Throwable", ex.getClass().getSimpleName()); + return true; + } + if (caughtType.isAssignableFrom(ex.getClass())) { + CircuitBreakerPolicy.logger.log(Level.FINER, "Exception {0} is a child of a Throwable: {1}", + new String[] { ex.getClass().getSimpleName(), caughtType.getSimpleName() }); + return true; + } + } + return false; + } } diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/model/RetryPolicy.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/RetryPolicy.java similarity index 58% rename from appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/model/RetryPolicy.java rename to appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/RetryPolicy.java index 5cd60a09123..7f27f1bbee5 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/model/RetryPolicy.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/RetryPolicy.java @@ -1,7 +1,9 @@ -package fish.payara.microprofile.faulttolerance.model; +package fish.payara.microprofile.faulttolerance.policy; import java.lang.reflect.Method; +import java.time.Duration; import java.time.temporal.ChronoUnit; +import java.util.concurrent.ThreadLocalRandom; import javax.interceptor.InvocationContext; @@ -14,6 +16,10 @@ */ public final class RetryPolicy extends Policy { + @SuppressWarnings("unchecked") + private static final RetryPolicy NONE = new RetryPolicy(null, 0, 0, ChronoUnit.SECONDS, 0, ChronoUnit.SECONDS, 0, + ChronoUnit.SECONDS, new Class[0], new Class[0]); + public final int maxRetries; public final long delay; public final ChronoUnit delayUnit; @@ -27,11 +33,13 @@ public final class RetryPolicy extends Policy { public RetryPolicy(Method annotatedMethod, int maxRetries, long delay, ChronoUnit delayUnit, long maxDuration, ChronoUnit durationUnit, long jitter, ChronoUnit jitterDelayUnit, Class[] retryOn, Class[] abortOn) { - checkAtLeast(-1, annotatedMethod, Retry.class, "maxRetries", maxRetries); - checkAtLeast(0, annotatedMethod, Retry.class, "delay", delay); - checkAtLeast(0, annotatedMethod, Retry.class, "maxDuration", maxDuration); - checkAtLeast("delay", delay + 1, annotatedMethod, Retry.class, "maxDuration", maxDuration); - checkAtLeast(0, annotatedMethod, Retry.class, "jitter", jitter); + if (annotatedMethod != null) { + checkAtLeast(-1, annotatedMethod, Retry.class, "maxRetries", maxRetries); + checkAtLeast(0, annotatedMethod, Retry.class, "delay", delay); + checkAtLeast(0, annotatedMethod, Retry.class, "maxDuration", maxDuration); + checkAtLeast("delay", delay + 1, annotatedMethod, Retry.class, "maxDuration", maxDuration); + checkAtLeast(0, annotatedMethod, Retry.class, "jitter", jitter); + } this.maxRetries = maxRetries; this.delay = delay; this.delayUnit = delayUnit; @@ -57,7 +65,29 @@ public static RetryPolicy create(InvocationContext context, FaultToleranceConfig config.retryOn(annotation, context), config.abortOn(annotation, context)); } - return null; + return NONE; + } + + public boolean retryOn(Exception ex) { + return isCaught(ex, retryOn) && !isCaught(ex, abortOn); + } + + public Long timeoutTimeNow() { + return maxDuration == 0L ? null : System.currentTimeMillis() + Duration.of(maxDuration, durationUnit).toMillis(); } + public boolean isDelayed() { + return delay > 0L || jitter > 0L; + } + + public long jitteredDelay() { + long duration = Duration.of(delay, delayUnit).toMillis(); + return jitter == 0L + ? duration + : duration + ThreadLocalRandom.current().nextLong(0, Duration.of(jitter, jitterDelayUnit).toMillis()); + } + + public int totalAttempts() { + return maxRetries < 0 ? Integer.MAX_VALUE : maxRetries + 1; + } } diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/model/TimeoutPolicy.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/TimeoutPolicy.java similarity index 87% rename from appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/model/TimeoutPolicy.java rename to appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/TimeoutPolicy.java index 841d9672b0c..713ab4133c4 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/model/TimeoutPolicy.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/TimeoutPolicy.java @@ -1,6 +1,7 @@ -package fish.payara.microprofile.faulttolerance.model; +package fish.payara.microprofile.faulttolerance.policy; import java.lang.reflect.Method; +import java.time.Duration; import java.time.temporal.ChronoUnit; import javax.interceptor.InvocationContext; @@ -32,4 +33,8 @@ public static TimeoutPolicy create(InvocationContext context, FaultToleranceConf } return null; } + + public long toMillis() { + return Duration.of(value, unit).toMillis(); + } } diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/state/CircuitBreakerState.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/state/CircuitBreakerState.java index a80507ba02c..1954bfe5b88 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/state/CircuitBreakerState.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/state/CircuitBreakerState.java @@ -53,7 +53,7 @@ */ public class CircuitBreakerState { - private static final Logger LOGGER = Logger.getLogger(CircuitBreakerState.class.getName()); + private static final Logger logger = Logger.getLogger(CircuitBreakerState.class.getName()); public enum CircuitState { OPEN, CLOSED, HALF_OPEN @@ -97,13 +97,13 @@ public void setCircuitState(CircuitState circuitState) { /** * Records a success or failure result to the CircuitBreaker. - * @param result True for a success, false for a failure + * @param success True for a success, false for a failure */ - public void recordClosedResult(Boolean result) { + public void recordClosedResult(boolean success) { // If the queue is full, remove the oldest result and add - if (!this.closedResultsQueue.offer(result)) { + if (!this.closedResultsQueue.offer(success)) { this.closedResultsQueue.poll(); - this.closedResultsQueue.offer(result); + this.closedResultsQueue.offer(success); } } @@ -158,7 +158,7 @@ public boolean isOverFailureThreshold(long failureThreshold) { } } } else { - LOGGER.log(Level.FINE, "CircuitBreaker results queue isn't full yet."); + logger.log(Level.FINE, "CircuitBreaker results queue isn't full yet."); } return over; @@ -174,16 +174,38 @@ public long updateAndGet(CircuitState circuitState) { : this.allStateTimes.get(circuitState).nanos(); } - public long open() { + public long isOpen() { return updateAndGet(CircuitState.OPEN); } - public long halfOpen() { + public long isHalfOpen() { return updateAndGet(CircuitState.HALF_OPEN); } - public long closed() { + public long isClosed() { return updateAndGet(CircuitState.CLOSED); } + public void close() { + setCircuitState(CircuitState.CLOSED); + resetHalfOpenSuccessfulResultCounter(); + resetResults(); + } + + public void open() { + setCircuitState(CircuitState.OPEN); + resetHalfOpenSuccessfulResultCounter(); + } + + public void halfOpen() { + logger.log(Level.FINE, "Setting CircuitBreaker state to half open"); + setCircuitState(CircuitState.HALF_OPEN); + } + + public void halfOpenSuccessful(int successThreshold) { + incrementHalfOpenSuccessfulResultCounter(); + if (getHalfOpenSuccessFulResultCounter() == successThreshold) { + close(); + } + } } From ef8dbdbab09dcda69a988ca7419b64520443044a Mon Sep 17 00:00:00 2001 From: Jan Bernitt Date: Tue, 16 Apr 2019 13:32:10 +0200 Subject: [PATCH 05/30] PAYARA-3468 TCK bulkhead, circuit breaker, config, disableEnv and fallbackmethod PASS; added unit tests for fallback method lookup and validation taken from TCK scenarios --- .../FaultToleranceApplicationState.java | 10 + .../FaultToleranceExecution.java | 8 +- .../faulttolerance/FaultToleranceService.java | 37 ++- .../cdi/CdiFaultToleranceConfig.java | 77 +++-- .../cdi/FaultToleranceCDIExtension.java | 6 +- .../cdi/FaultToleranceCdiUtils.java | 206 +++---------- .../FaultToleranceInterceptor.java | 19 +- .../policy/CircuitBreakerPolicy.java | 2 +- .../faulttolerance/policy/FallbackPolicy.java | 34 +- .../policy/FaultTolerancePolicy.java | 65 ++-- .../policy/MethodLookupUtils.java | 90 ++++++ .../faulttolerance/policy/Policy.java | 15 +- .../policy/StaticAnalysisMethodContext.java | 57 ++++ .../policy/FallbackMethodBean.java | 19 ++ .../policy/FallbackMethodBeanA.java | 103 +++++++ .../policy/FallbackMethodBeanB.java | 78 +++++ .../policy/FallbackMethodMissingTest.java | 113 +++++++ .../policy/FallbackMethodTest.java | 291 ++++++++++++++++++ .../policy/sub/FallbackMethodBeanC.java | 13 + 19 files changed, 974 insertions(+), 269 deletions(-) create mode 100644 appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/MethodLookupUtils.java create mode 100644 appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/StaticAnalysisMethodContext.java create mode 100644 appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/policy/FallbackMethodBean.java create mode 100644 appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/policy/FallbackMethodBeanA.java create mode 100644 appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/policy/FallbackMethodBeanB.java create mode 100644 appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/policy/FallbackMethodMissingTest.java create mode 100644 appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/policy/FallbackMethodTest.java create mode 100644 appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/policy/sub/FallbackMethodBeanC.java diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/FaultToleranceApplicationState.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/FaultToleranceApplicationState.java index ce6c5d4c7a0..0a0e72552a4 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/FaultToleranceApplicationState.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/FaultToleranceApplicationState.java @@ -43,6 +43,7 @@ import fish.payara.microprofile.faulttolerance.state.CircuitBreakerState; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicReference; /** * @@ -53,6 +54,8 @@ public class FaultToleranceApplicationState { private final Map> circuitBreakerStates = new ConcurrentHashMap<>(); private final Map> bulkheadExecutionSemaphores = new ConcurrentHashMap<>(); private final Map> bulkheadExecutionQueueSemaphores = new ConcurrentHashMap<>(); + private final AtomicReference config = new AtomicReference<>(); + private final AtomicReference metrics = new AtomicReference<>(); public Map> getCircuitBreakerStates() { return circuitBreakerStates; @@ -66,4 +69,11 @@ public Map> getBulkheadExecutionQueueSema return bulkheadExecutionQueueSemaphores; } + public AtomicReference getConfig() { + return config; + } + + public AtomicReference getMetrics() { + return metrics; + } } diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/FaultToleranceExecution.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/FaultToleranceExecution.java index a344595fad6..66e59325d07 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/FaultToleranceExecution.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/FaultToleranceExecution.java @@ -1,5 +1,6 @@ package fish.payara.microprofile.faulttolerance; +import java.lang.reflect.Method; import java.util.concurrent.Callable; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Future; @@ -8,11 +9,16 @@ import org.eclipse.microprofile.faulttolerance.FallbackHandler; +import fish.payara.microprofile.faulttolerance.cdi.FaultToleranceCdiUtils.Stereotypes; import fish.payara.microprofile.faulttolerance.state.BulkheadSemaphore; import fish.payara.microprofile.faulttolerance.state.CircuitBreakerState; public interface FaultToleranceExecution { + FaultToleranceConfig getConfig(InvocationContext context, Stereotypes stereotypes); + + FaultToleranceMetrics getMetrics(InvocationContext context); + CircuitBreakerState getState(int requestVolumeThreshold, InvocationContext context); BulkheadSemaphore getExecutionSemaphoreOf(int maxConcurrentThreads, InvocationContext context); @@ -30,7 +36,7 @@ public interface FaultToleranceExecution { Object fallbackHandle(Class> fallbackClass, InvocationContext context, Exception exception) throws Exception; - Object fallbackInvoke(String fallbackMethod, InvocationContext context) throws Exception; + Object fallbackInvoke(Method fallbackMethod, InvocationContext context) throws Exception; void startTrace(String method, InvocationContext context); diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/FaultToleranceService.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/FaultToleranceService.java index 36bef4a3801..78df876aebf 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/FaultToleranceService.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/FaultToleranceService.java @@ -39,8 +39,12 @@ */ package fish.payara.microprofile.faulttolerance; +import fish.payara.microprofile.faulttolerance.cdi.CdiFaultToleranceConfig; +import fish.payara.microprofile.faulttolerance.cdi.CdiFaultToleranceMetrics; import fish.payara.microprofile.faulttolerance.cdi.FaultToleranceCdiUtils; +import fish.payara.microprofile.faulttolerance.cdi.FaultToleranceCdiUtils.Stereotypes; import fish.payara.microprofile.faulttolerance.policy.AsynchronousPolicy; +import fish.payara.microprofile.faulttolerance.policy.FallbackPolicy; import fish.payara.microprofile.faulttolerance.state.BulkheadSemaphore; import fish.payara.microprofile.faulttolerance.state.CircuitBreakerState; import fish.payara.notification.requesttracing.RequestTraceSpan; @@ -117,7 +121,7 @@ public class FaultToleranceService implements EventListener, FaultToleranceExecu private final Map stateByApplication = new ConcurrentHashMap<>(); private ManagedScheduledExecutorService defaultScheduledExecutorService; private ManagedExecutorService defaultExecutorService; - + @PostConstruct public void postConstruct() throws NamingException { events.register(this); @@ -138,6 +142,20 @@ public void event(Event event) { } } + @Override + public FaultToleranceConfig getConfig(InvocationContext context, Stereotypes stereotypes) { + FaultToleranceApplicationState appState = getApplicationState(getApplicationContext(context)); + return appState.getConfig() + .updateAndGet(config -> config != null ? config : new CdiFaultToleranceConfig(null, stereotypes)); + } + + @Override + public FaultToleranceMetrics getMetrics(InvocationContext context) { + FaultToleranceApplicationState appState = getApplicationState(getApplicationContext(context)); + return appState.getMetrics() + .updateAndGet(metrics -> metrics != null ? metrics : new CdiFaultToleranceMetrics(null)); + } + //TODO use the scheduler to schedule a clean of FT Info private ManagedExecutorService getManagedExecutorService() { @@ -202,7 +220,7 @@ private void deregisterApplication(String applicationName) { * @param context The context of the current invocation * @return The application name */ - private String getApplicationName(InvocationContext context) { + private String getApplicationContext(InvocationContext context) { ComponentInvocation currentInvocation = invocationManager.getCurrentInvocation(); String appName = currentInvocation.getAppName(); if (appName != null) { @@ -267,19 +285,19 @@ private void addGenericFaultToleranceRequestTracingDetails(RequestTraceSpan span @Override public CircuitBreakerState getState(int requestVolumeThreshold, InvocationContext context) { - return getCircuitBreakerState(getApplicationName(context), context.getTarget(), + return getCircuitBreakerState(getApplicationContext(context), context.getTarget(), context.getMethod(), requestVolumeThreshold); } @Override public BulkheadSemaphore getExecutionSemaphoreOf(int maxConcurrentThreads, InvocationContext context) { - return getBulkheadExecutionSemaphore(getApplicationName(context), + return getBulkheadExecutionSemaphore(getApplicationContext(context), context.getTarget(), context.getMethod(), maxConcurrentThreads); } @Override public BulkheadSemaphore getWaitingQueueSemaphoreOf(int queueCapacity, InvocationContext context) { - return getBulkheadExecutionQueueSemaphore(getApplicationName(context), + return getBulkheadExecutionQueueSemaphore(getApplicationContext(context), context.getTarget(), context.getMethod(), queueCapacity); } @@ -332,14 +350,13 @@ public Object fallbackHandle(Class> fallbackClass, } @Override - public Object fallbackInvoke(String fallbackMethod, InvocationContext context) throws Exception { + public Object fallbackInvoke(Method fallbackMethod, InvocationContext context) throws Exception { try { - return FaultToleranceCdiUtils.getAnnotatedMethodClass(context, Fallback.class) - .getDeclaredMethod(fallbackMethod, context.getMethod().getParameterTypes()) - .invoke(context.getTarget(), context.getParameters()); + fallbackMethod.setAccessible(true); + return fallbackMethod.invoke(context.getTarget(), context.getParameters()); } catch (InvocationTargetException e) { throw (Exception) e.getTargetException(); - } catch (IllegalAccessException | NoSuchMethodException e) { + } catch (IllegalAccessException e) { throw new FaultToleranceDefinitionException(e); // should not happen as we validated } } diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/cdi/CdiFaultToleranceConfig.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/cdi/CdiFaultToleranceConfig.java index 1909cd39509..e22a0186c4d 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/cdi/CdiFaultToleranceConfig.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/cdi/CdiFaultToleranceConfig.java @@ -5,8 +5,7 @@ import java.time.temporal.ChronoUnit; import java.util.ArrayList; import java.util.List; -import java.util.NoSuchElementException; -import java.util.Optional; +import java.util.concurrent.atomic.AtomicReference; import java.util.logging.Level; import java.util.logging.Logger; @@ -37,6 +36,13 @@ public class CdiFaultToleranceConfig implements FaultToleranceConfig, Serializab private static final Logger logger = Logger.getLogger(CdiFaultToleranceConfig.class.getName()); + /** + * These two property should only be read once at the start of the application, therefore they are cached in static + * field. + */ + private final AtomicReference nonFallbackEnabled = new AtomicReference<>(); + private final AtomicReference metricsEnabled = new AtomicReference<>(); + private final Stereotypes sterotypes; private transient Config config; @@ -64,17 +70,26 @@ private Config getConfig() { @Override public boolean isEnabled(InvocationContext context) { - return getConfig().getOptionalValue(FAULT_TOLERANCE_ENABLED_PROPERTY, Boolean.class).orElse(true); + if (nonFallbackEnabled.get() == null) { + nonFallbackEnabled.compareAndSet(null, + getConfig().getOptionalValue(FAULT_TOLERANCE_ENABLED_PROPERTY, Boolean.class).orElse(true)); + } + return nonFallbackEnabled.get().booleanValue(); } @Override - public boolean isEnabled(Class annotationType, InvocationContext context) { - return FaultToleranceCdiUtils.getEnabledOverrideValue(getConfig(), annotationType, context).orElse(true); + public boolean isMetricsEnabled(InvocationContext context) { + if (metricsEnabled.get() == null) { + metricsEnabled.compareAndSet(null, + getConfig().getOptionalValue(METRICS_ENABLED_PROPERTY, Boolean.class).orElse(true)); + } + return metricsEnabled.get().booleanValue(); } @Override - public boolean isMetricsEnabled(InvocationContext context) { - return getConfig().getOptionalValue(METRICS_ENABLED_PROPERTY, Boolean.class).orElse(true); + public boolean isEnabled(Class annotationType, InvocationContext context) { + return FaultToleranceCdiUtils.getEnabledOverrideValue(getConfig(), annotationType, context, + annotationType == Fallback.class || isEnabled(context)); } @Override @@ -204,17 +219,17 @@ public ChronoUnit unit(Timeout annotation, InvocationContext context) { @SuppressWarnings("unchecked") @Override public Class> value(Fallback annotation, InvocationContext context) { - Optional className = FaultToleranceCdiUtils.getOverrideValue(getConfig(), Fallback.class, "value", - context, String.class); - if (className.isPresent()) { - try { - return (Class>) Thread.currentThread().getContextClassLoader() - .loadClass(className.get()); - } catch (ClassNotFoundException e) { - // fall through - } + String className = FaultToleranceCdiUtils.getOverrideValue(getConfig(), Fallback.class, "value", + context, String.class, null); + if (className == null) { + return annotation.value(); + } + try { + return (Class>) Thread.currentThread().getContextClassLoader() + .loadClass(className); + } catch (ClassNotFoundException e) { + return annotation.value(); } - return annotation.value(); } @Override @@ -244,30 +259,28 @@ private ChronoUnit chronoUnitValue(Class annotationType, S private T value(Class annotationType, String attribute, InvocationContext context, Class valueType, T annotationValue) { - return FaultToleranceCdiUtils.getOverrideValue(getConfig(), annotationType, attribute, context, valueType) - .orElse(annotationValue); + return FaultToleranceCdiUtils.getOverrideValue(getConfig(), annotationType, attribute, context, valueType, annotationValue); } private Class[] getClassArrayValue(Class annotationType, String attributeName, InvocationContext context, Class[] annotationValue) { + String classNames = FaultToleranceCdiUtils.getOverrideValue(getConfig(), annotationType, attributeName, context, + String.class, null); + if (classNames == null) { + return annotationValue; + } try { - Optional classNames = FaultToleranceCdiUtils.getOverrideValue( - getConfig(), annotationType, attributeName, context, String.class); - if (classNames.isPresent()) { - List> classList = new ArrayList<>(); - // Remove any curly or square brackets from the string, as well as any spaces and ".class"es - for (String className : classNames.get().replaceAll("[\\{\\[ \\]\\}]", "") - .replaceAll("\\.class", "").split(",")) { - classList.add(Class.forName(className)); - } - return classList.toArray(annotationValue); + List> classList = new ArrayList<>(); + // Remove any curly or square brackets from the string, as well as any spaces and ".class"es + for (String className : classNames.replaceAll("[\\{\\[ \\]\\}]", "").replaceAll("\\.class", "") + .split(",")) { + classList.add(Class.forName(className)); } - } catch (NoSuchElementException nsee) { - logger.log(Level.FINER, "Could not find element in config", nsee); + return classList.toArray(annotationValue); } catch (ClassNotFoundException cnfe) { logger.log(Level.INFO, "Could not find class from " + attributeName + " config, defaulting to annotation. " + "Make sure you give the full canonical class name.", cnfe); + return annotationValue; } - return annotationValue; } } diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/cdi/FaultToleranceCDIExtension.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/cdi/FaultToleranceCDIExtension.java index 2d53593560a..b179cd36c2b 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/cdi/FaultToleranceCDIExtension.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/cdi/FaultToleranceCDIExtension.java @@ -41,6 +41,7 @@ import fish.payara.microprofile.faulttolerance.interceptors.FaultToleranceBehaviour; import fish.payara.microprofile.faulttolerance.interceptors.FaultToleranceInterceptor; +import fish.payara.microprofile.faulttolerance.policy.FaultTolerancePolicy; import java.lang.annotation.Annotation; import java.lang.reflect.Method; @@ -82,7 +83,7 @@ void beforeBeanDiscovery(@Observes BeforeBeanDiscovery beforeBeanDiscovery, Bean void processAnnotatedType(@Observes @WithAnnotations({ Asynchronous.class, Bulkhead.class, CircuitBreaker.class, Fallback.class, Retry.class, Timeout.class }) ProcessAnnotatedType processAnnotatedType, BeanManager beanManager) throws Exception { - mark(processAnnotatedType); + validateAndMark(processAnnotatedType); } /** @@ -91,10 +92,11 @@ void processAnnotatedType(@Observes @WithAnnotations({ Asynchronous.class, B * * @param processAnnotatedType type currently processed */ - private static void mark(ProcessAnnotatedType processAnnotatedType) { + private static void validateAndMark(ProcessAnnotatedType processAnnotatedType) { boolean markAllMethods = isAnnotaetdWithFaultToleranceAnnotations(processAnnotatedType.getAnnotatedType()); for (AnnotatedMethodConfigurator methodConfigurator : processAnnotatedType.configureAnnotatedType().methods()) { if (markAllMethods || isAnnotaetdWithFaultToleranceAnnotations(methodConfigurator.getAnnotated())) { + FaultTolerancePolicy.asAnnotated(methodConfigurator.getAnnotated().getJavaMember()); methodConfigurator.add(MARKER); } } diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/cdi/FaultToleranceCdiUtils.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/cdi/FaultToleranceCdiUtils.java index 7da6d1a1261..0f34ca94177 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/cdi/FaultToleranceCdiUtils.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/cdi/FaultToleranceCdiUtils.java @@ -72,7 +72,6 @@ public interface Stereotypes { */ public static A getAnnotation(Stereotypes sterotypes, Class annotationClass, InvocationContext context) { - Class annotatedClass = getAnnotatedMethodClass(context, annotationClass); logger.log(Level.FINER, "Attempting to get annotation {0} from {1}", new String[]{annotationClass.getSimpleName(), context.getMethod().getName()}); // Try to get the annotation from the method, otherwise attempt to get it from the class @@ -80,6 +79,7 @@ public static A getAnnotation(Stereotypes sterotypes, Cla logger.log(Level.FINER, "Annotation was directly present on the method"); return context.getMethod().getAnnotation(annotationClass); } + Class annotatedClass = getAnnotatedMethodClass(context, annotationClass); if (annotatedClass.isAnnotationPresent(annotationClass)) { logger.log(Level.FINER, "Annotation was directly present on the class"); return annotatedClass.getAnnotation(annotationClass); @@ -88,13 +88,13 @@ public static A getAnnotation(Stereotypes sterotypes, Cla return null; } logger.log(Level.FINER, "Annotation wasn't directly present on the method or class, checking stereotypes"); - // Account for Stereotypes for (Annotation annotation : annotatedClass.getAnnotations()) { Class annotationType = annotation.annotationType(); if (annotationType == annotationClass) { logger.log(Level.FINER, "Annotation was found in a stereotype"); return annotationClass.cast(annotation); } + // Account for Stereotypes if (sterotypes.isStereotype(annotationType)) { for (Annotation metaAnnotation : sterotypes.getStereotypeDefinition(annotationType)) { if (metaAnnotation.annotationType() == annotationClass) { @@ -118,6 +118,7 @@ public static A getAnnotation(Stereotypes sterotypes, Cla * @param parameterType The type of the parameter to get the override value of * @return */ + @Deprecated //TODO remove with validators public static Optional getOverrideValue(Config config, Class annotationClass, String parameterName, String annotatedMethodName, String annotatedClassCanonicalName, Class parameterType) { Optional value = Optional.empty(); @@ -159,56 +160,15 @@ public static Optional getOverrideValue(Config conf * Gets overriding config parameter values if they're present from an invocation context. * @param The annotation type * @param config The config to get the overriding parameter values from - * @param annotationClass The annotation class + * @param annotationType The annotation class * @param parameterName The name of the parameter to get the override value of * @param context The context of the invoking request * @param parameterType The type of the parameter to get the override value of * @return */ - public static Optional getOverrideValue(Config config, Class annotationClass, - String parameterName, InvocationContext context, Class parameterType) { - Optional value = Optional.empty(); - - // Get the annotation, method, and class names - String annotationName = annotationClass.getSimpleName(); - String annotatedMethodName = context.getMethod().getName(); - String annotatedClassCanonicalName = getAnnotatedMethodClassCanonicalName( - getAnnotatedMethodClass(context, annotationClass)); - - // Check if there's a config override - if (config != null) { - logger.log(Level.FINER, "Getting config override for annotated method..."); - - // Check if there's a config override for this specific method - value = config.getOptionalValue(annotatedClassCanonicalName + "/" - + annotatedMethodName + "/" + annotationName + "/" + parameterName, parameterType); - - if (!value.isPresent()) { - logger.log(Level.FINER, "No config override for annotated method, checking if the method is " - + "annotated directly..."); - // If the method is annotated directly, check for a global level override and return - if (context.getMethod().getAnnotation(annotationClass) != null) { - logger.log(Level.FINER, "Method is annotated directly, checking for global override..."); - // The only thing that should override a method-level annotation is a method or global-level config - return checkForGlobalLevelOverride(annotationName, parameterName, parameterType, config); - } - - // If the method wasn't annotated directly, check if there's an override for the class - if (!value.isPresent()) { - value = checkForClassLevelOverride(annotatedClassCanonicalName, annotationName, parameterName, - parameterType, config); - - // If there wasn't a config override for the class, check if there's a global one - if (!value.isPresent()) { - value = checkForGlobalLevelOverride(annotationName, parameterName, parameterType, config); - } - } - } - } else { - logger.log(Level.FINE, "No config to get override parameters from."); - } - - return value; + public static T getOverrideValue(Config config, Class annotationType, + String parameterName, InvocationContext context, Class parameterType, T defaultValue) { + return getOverrideValue(config, context, annotationType, parameterName, parameterType, defaultValue, true); } /** @@ -217,123 +177,39 @@ public static Optional getOverrideValue(Config conf * * @param The annotation type * @param config The config to get the overriding enabled value from - * @param annotationClass The annotation class + * @param annotationType The annotation class * @param context The context of the invoking request * @return */ - public static Optional getEnabledOverrideValue(Config config, Class annotationClass, - InvocationContext context) { - Optional value = Optional.empty(); - - // Get the annotation, method, and class names - String annotationName = annotationClass.getSimpleName(); - String annotatedMethodName = context.getMethod().getName(); - String annotatedClassCanonicalName = getAnnotatedMethodClassCanonicalName( - getAnnotatedMethodClass(context, annotationClass)); - - // Check if there's a config override - if (config != null) { - logger.log(Level.FINER, "Getting config override for annotated method..."); - - // Check if there's a config override for this specific method - value = config.getOptionalValue(annotatedClassCanonicalName + "/" - + annotatedMethodName + "/" + annotationName + "/" + "enabled", Boolean.class); - - if (!value.isPresent()) { - logger.log(Level.FINER, "No config override for annotated method, checking if the method is " - + "annotated directly..."); - // If the method is annotated directly, check for a cless or global-level override - if (context.getMethod().getAnnotation(annotationClass) != null) { - logger.log(Level.FINER, "Method is annotated directly, checking for class or global override..."); - checkAnnotatedMethodForEnabledOverride(annotatedClassCanonicalName, annotationName, config); - } - - // If the method wasn't annotated directly, check if there's an override for the class - if (!value.isPresent()) { - value = checkForClassLevelOverride(annotatedClassCanonicalName, annotationName, "enabled", - Boolean.class, config); - - // If there wasn't a config override for the class, check if there's a global one - if (!value.isPresent()) { - value = checkForGlobalLevelOverride(annotationName, "enabled", Boolean.class, config); - } - } - } - } else { - logger.log(Level.FINE, "No config to get override parameters from."); - } - - return value; + public static boolean getEnabledOverrideValue(Config config, Class annotationType, + InvocationContext context, boolean defaultValue) { + return getOverrideValue(config, context, annotationType, "enabled", Boolean.class, defaultValue, false); } - /** - * Helper method that logs whether an override was found. - * @param value The Optional value to check if an override was found for - * @param level A String denoting the level of override you were checking for - */ - private static void logOverride(Optional value, String level) { - if (value.isPresent()) { - logger.log(Level.FINER, "{0} override found.", level); - } else { - logger.log(Level.FINER, "No config overrides."); + private static T getOverrideValue(Config config, InvocationContext context, + Class annotationType, String propertyName, Class resultType, T defaultValue, + boolean requiresAnnotation) { + Class targetClass = getAnnotatedMethodClass(context, annotationType); + String annotationName = annotationType.getSimpleName(); + String methodName = context.getMethod().getName(); + String className = getPlainCanonicalName(targetClass); + + String key = String.format("%s/%s/%s/%s", className, methodName, annotationName, propertyName); + Optional overrideValue = config.getOptionalValue(key, resultType); + boolean methodAnnotated = context.getMethod().isAnnotationPresent(annotationType); + if (overrideValue.isPresent() && (!requiresAnnotation || methodAnnotated)) { + return overrideValue.get(); } - } - - /** - * Helper method that checks if a directly annotated method has a class or global-level override for the enabled - * parameter. This gets its own method as the enabled parameter has a different override priority. - * @param annotatedClassCanonicalName The canonical name of the annotated class - * @param annotationName The name of the annotation - * @param config The config to get the overriding enabled value from - * @return An overriding enabled value if there is one - */ - private static Optional checkAnnotatedMethodForEnabledOverride(String annotatedClassCanonicalName, - String annotationName, Config config) { - Optional value = checkForClassLevelOverride(annotatedClassCanonicalName, annotationName, "enabled", - Boolean.class, config); - - if (!value.isPresent()) { - value = checkForGlobalLevelOverride(annotationName, "enabled", Boolean.class, config); + if (!requiresAnnotation || !methodAnnotated) { + key = String.format("%s/%s/%s", className, annotationName, propertyName); + overrideValue = config.getOptionalValue(key, resultType); + if (overrideValue.isPresent() && (!requiresAnnotation || targetClass.isAnnotationPresent(annotationType))) { + return overrideValue.get(); + } } - - return value; - } - - /** - * Helper method that checks if there is an override for a parameter at the class level. - * @param annotatedClassCanonicalName The canonical name of the annotated class - * @param annotationName The name of the annotation - * @param parameterName The name of the parameter - * @param parameterType The parameter class - * @param config The config to get the overriding value from - * @return An overriding value for the provided parameter if there is one - */ - private static Optional checkForClassLevelOverride(String annotatedClassCanonicalName, String annotationName, - String parameterName, Class parameterType, Config config) { - logger.log(Level.FINER, "No config override for annotated method, getting config override for the " - + "annotated class..."); - Optional value = config.getOptionalValue(annotatedClassCanonicalName + "/" + annotationName - + "/" + parameterName, parameterType); - logOverride(value, "Class-level"); - - return value; - } - - /** - * Helper method that checks if there is an override for a parameter at the global level. - * @param annotationName The name of the annotation - * @param parameterName The name of the parameter - * @param parameterType The parameter class - * @param config The config to get the overriding value from - * @return - */ - private static Optional checkForGlobalLevelOverride(String annotationName, String parameterName, Class parameterType, - Config config) { - logger.log(Level.FINER, "No config override for annotated class, checking for global override."); - Optional value = config.getOptionalValue(annotationName + "/" + parameterName, parameterType); - logOverride(value, "Global"); - - return value; + key = String.format("%s/%s", annotationName, propertyName); + overrideValue = config.getOptionalValue(key, resultType); + return overrideValue.orElse(defaultValue); } /** @@ -347,7 +223,6 @@ private static Optional checkForGlobalLevelOverride(String annotationName public static Class getAnnotatedMethodClass(InvocationContext context, Class annotationClass) { Class targetClass = context.getTarget().getClass(); - if (targetClass.isAnnotationPresent(annotationClass)) { return targetClass; } @@ -358,18 +233,17 @@ public static Class getAnnotatedMethodClass(Invocation * Helper method that gets the canonical name of an annotated class. This is used to strip out any Weld proxy * naming that gets appended to the real class name. * @param The class of the annotation - * @param annotatedClass The class of the annotation + * @param type The class of the annotation * @return The canonical name of the annotated method's class */ - public static String getAnnotatedMethodClassCanonicalName(Class annotatedClass) { - String canonicalClassName = annotatedClass.getCanonicalName(); - + public static String getPlainCanonicalName(Class type) { + String canonicalName = type.getCanonicalName(); // If the class was a proxy from Weld, cut away the appended gubbins // Author Note: There's probably a better way of doing this... - if (canonicalClassName.contains("$Proxy$_$$_WeldSubclass")) { - return canonicalClassName.split("\\$Proxy\\$_\\$\\$_WeldSubclass")[0]; + if (canonicalName.contains("$Proxy$_$$_WeldSubclass")) { + return canonicalName.split("\\$Proxy\\$_\\$\\$_WeldSubclass")[0]; } - return canonicalClassName; + return canonicalName; } /** @@ -381,7 +255,7 @@ public static String getAnnotatedMethodClassCanonicalName */ public static String getFullAnnotatedMethodSignature(InvocationContext context, Class annotationClass) { - return getAnnotatedMethodClassCanonicalName(getAnnotatedMethodClass(context, annotationClass)) + "." + return getPlainCanonicalName(getAnnotatedMethodClass(context, annotationClass)) + "." + context.getMethod().getName(); } } diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/FaultToleranceInterceptor.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/FaultToleranceInterceptor.java index 5970448bb62..888cb7d4a7b 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/FaultToleranceInterceptor.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/FaultToleranceInterceptor.java @@ -16,11 +16,7 @@ import org.eclipse.microprofile.faulttolerance.exceptions.FaultToleranceDefinitionException; import org.glassfish.internal.api.Globals; -import fish.payara.microprofile.faulttolerance.FaultToleranceConfig; import fish.payara.microprofile.faulttolerance.FaultToleranceExecution; -import fish.payara.microprofile.faulttolerance.FaultToleranceMetrics; -import fish.payara.microprofile.faulttolerance.cdi.CdiFaultToleranceConfig; -import fish.payara.microprofile.faulttolerance.cdi.CdiFaultToleranceMetrics; import fish.payara.microprofile.faulttolerance.cdi.FaultToleranceCdiUtils.Stereotypes; import fish.payara.microprofile.faulttolerance.policy.FaultTolerancePolicy; @@ -37,12 +33,11 @@ public class FaultToleranceInterceptor implements Stereotypes, Serializable { @AroundInvoke public Object intercept(InvocationContext context) throws Exception { try { - FaultTolerancePolicy policy = FaultTolerancePolicy.get(context, this::getConfig); - if (policy.isFaultToleranceEnabled) { - FaultToleranceExecution execution = - Globals.getDefaultBaseServiceLocator().getService(FaultToleranceExecution.class); - FaultToleranceMetrics metrics = policy.isMetricsEnabled ? new CdiFaultToleranceMetrics(null) : null; - return policy.processWithFaultTolerance(context, execution, metrics); + FaultToleranceExecution execution = + Globals.getDefaultBaseServiceLocator().getService(FaultToleranceExecution.class); + FaultTolerancePolicy policy = FaultTolerancePolicy.get(context, () -> execution.getConfig(context, this)); + if (policy.isPresent) { + return policy.proceed(context, execution); } } catch (FaultToleranceDefinitionException e) { logger.log(Level.SEVERE, "Effective FT policy contains illegal values, fault tolerance cannot be applied," @@ -52,10 +47,6 @@ public Object intercept(InvocationContext context) throws Exception { return context.proceed(); } - private FaultToleranceConfig getConfig() { - return new CdiFaultToleranceConfig(null, this); - } - @Override public boolean isStereotype(Class annotationType) { return beanManager.isStereotype(annotationType); diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/CircuitBreakerPolicy.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/CircuitBreakerPolicy.java index c76897a996a..c05fedd15b8 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/CircuitBreakerPolicy.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/CircuitBreakerPolicy.java @@ -16,7 +16,7 @@ public final class CircuitBreakerPolicy extends Policy { static final Logger logger = Logger.getLogger(CircuitBreakerPolicy.class.getName()); - + public final Class[] failOn; public final long delay; public final ChronoUnit delayUnit; diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/FallbackPolicy.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/FallbackPolicy.java index 7bb937f5242..1c8a4ad749f 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/FallbackPolicy.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/FallbackPolicy.java @@ -10,6 +10,7 @@ import org.eclipse.microprofile.faulttolerance.exceptions.FaultToleranceDefinitionException; import fish.payara.microprofile.faulttolerance.FaultToleranceConfig; +import javassist.Modifier; /** * The resolved "cached" information of a {@link Fallback} annotation an a specific method. @@ -20,24 +21,24 @@ public final class FallbackPolicy extends Policy { public final String fallbackMethod; public final Method method; - public FallbackPolicy(Method method, Class> value, String fallbackMethod) { + public FallbackPolicy(Method annotated, Class> value, String fallbackMethod) { checkUnambiguous(value, fallbackMethod); + this.value = value; + this.fallbackMethod = fallbackMethod; if (fallbackMethod != null && !fallbackMethod.isEmpty()) { - checkReturnsSameAs(method, Fallback.class, "fallbackMethod", method.getDeclaringClass(), fallbackMethod, - method.getParameterTypes()); - try { - this.method = method.getDeclaringClass().getDeclaredMethod(fallbackMethod, method.getParameterTypes()); - } catch (NoSuchMethodException | SecurityException e) { - throw new FaultToleranceDefinitionException(e); + method = MethodLookupUtils.findMethodWithMatchingNameAndArguments(fallbackMethod, annotated); + if (method == null) { + throw new FaultToleranceDefinitionException("Fallback method \"" + fallbackMethod + "\" not defined for " + + annotated.getDeclaringClass().getName()); } + checkReturnsSameAs(annotated, Fallback.class, "fallbackMethod", method); + checkAccessible(annotated, method); } else { - this.method = null; + method = null; } - this.value = value; if (isHandlerPresent()) { - checkReturnsSameAs(method, Fallback.class, "value", value, "handle", ExecutionContext.class); + checkReturnsSameAs(annotated, Fallback.class, "value", value, "handle", ExecutionContext.class); } - this.fallbackMethod = fallbackMethod; } public static FallbackPolicy create(InvocationContext context, FaultToleranceConfig config) { @@ -56,7 +57,18 @@ private static void checkUnambiguous(Class> value, } } + private static void checkAccessible(Method annotated, Method fallback) { + boolean samePackage = fallback.getDeclaringClass().getPackage().equals(annotated.getDeclaringClass().getPackage()); + boolean sameClass = fallback.getDeclaringClass().equals(annotated.getDeclaringClass()); + if (Modifier.isPackage(fallback.getModifiers()) && !samePackage + || Modifier.isPrivate(fallback.getModifiers()) && !sameClass) { + throw new FaultToleranceDefinitionException(describe(annotated, Fallback.class, "fallbackMethod") + + "that refers to a method that is not accessible."); + } + } + public boolean isHandlerPresent() { return value != null && value != Fallback.DEFAULT.class; } + } diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/FaultTolerancePolicy.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/FaultTolerancePolicy.java index f556f59fda1..e574aa1e95c 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/FaultTolerancePolicy.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/FaultTolerancePolicy.java @@ -54,6 +54,10 @@ public static void clean() { POLICY_BY_METHOD.entrySet().removeIf(entry -> now > entry.getValue().expiresMillis); } + public static FaultTolerancePolicy asAnnotated(Method annotated) { + return create(new StaticAnalysisMethodContext(annotated), () -> FaultToleranceConfig.ANNOTATED); + } + /** * Returns the {@link FaultTolerancePolicy} to use for the method invoked in the current context. * @@ -65,18 +69,16 @@ public static void clean() { */ public static FaultTolerancePolicy get(InvocationContext context, Supplier configSpplier) throws FaultToleranceDefinitionException { - return POLICY_BY_METHOD.compute(context.getMethod(), (method, info) -> - info != null && !info.isExpired() ? info : create(context, configSpplier)); + return POLICY_BY_METHOD.compute(context.getMethod(), (method, policy) -> + policy != null && !policy.isExpired() ? policy : create(context, configSpplier)); } private static FaultTolerancePolicy create(InvocationContext context, Supplier configSpplier) { FaultToleranceConfig config = configSpplier.get(); boolean isFaultToleranceEnabled = config.isEnabled(context); - if (!isFaultToleranceEnabled) { - return new FaultTolerancePolicy(false, false, null, null, null, null, null, null); - } + boolean metricsEnabled = config.isMetricsEnabled(context); return new FaultTolerancePolicy(isFaultToleranceEnabled, - config.isMetricsEnabled(context), + metricsEnabled, AsynchronousPolicy.create(context, config), BulkheadPolicy.create(context, config), CircuitBreakerPolicy.create(context, config), @@ -86,6 +88,7 @@ private static FaultTolerancePolicy create(InvocationContext context, Supplier asyncTry = new CompletableFuture<>(); + //TODO try if it works to use the asyncTry thread as inv asyncResult invocation.execution.runAsynchronous(asyncTry, () -> invocation.runStageWithWorker(inv -> processCircuitBreakerStage(inv, asyncTry))); try { asyncTry.get(); // wait and only proceed on success - if (invocation.asyncResult.isDone()) { // maybe another try finished by now? - throw new TimeoutException(); - } + //TODO this below check should not be needed since it only could be done from a another try which only riggers if this fails with an exception + // BUT need to remember that cancel can occur + invocation.timeoutIfConcludedConcurrently(); return asyncTry; } catch (ExecutionException ex) { throw (Exception) ex.getCause(); @@ -334,18 +349,14 @@ private Object processTimeoutStage(FaultToleranceInvocation invocation, Completa Thread.interrupted(); // clear the flag } if (didTimeout.get() || System.currentTimeMillis() > timeoutTime) { - logger.info("throwing TimeoutException"); throw new TimeoutException(); } - logger.info("returning "+resultValue); return resultValue; } catch (Exception ex) { if ((ex instanceof InterruptedException || ex.getCause() instanceof InterruptedException) && System.currentTimeMillis() > timeoutTime) { - logger.info("throwing TimeoutException after interrupted"); throw new TimeoutException(ex); } - logger.info("throwing a "+ex.getClass().getSimpleName()); throw ex; } finally { timeout.cancel(true); @@ -374,6 +385,7 @@ private Object processBulkheadStage(FaultToleranceInvocation invocation) throws BulkheadSemaphore waitingQueueSemaphore = invocation.execution.getWaitingQueueSemaphoreOf(bulkhead.waitingTaskQueue, invocation.context); if (waitingQueueSemaphore.tryAcquire(0, TimeUnit.SECONDS)) { try { + invocation.timeoutIfConcludedConcurrently(); // avoid execution if not needed executionSemaphore.acquire(); // block until execution permit becomes available } catch (InterruptedException ex) { waitingQueueSemaphore.release(); @@ -393,9 +405,8 @@ private Object processBulkheadStage(FaultToleranceInvocation invocation) throws * Stage where the actual wrapped method call occurs. */ private Object processMethodCallStage(FaultToleranceInvocation invocation) throws Exception { - if (isAsynchronous() && invocation.asyncResult.isDone()) { - throw new TimeoutException(); - } + invocation.timeoutIfConcludedConcurrently(); return invocation.context.proceed(); } + } diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/MethodLookupUtils.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/MethodLookupUtils.java new file mode 100644 index 00000000000..91d2d5777c3 --- /dev/null +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/MethodLookupUtils.java @@ -0,0 +1,90 @@ +package fish.payara.microprofile.faulttolerance.policy; + +import java.lang.reflect.GenericArrayType; +import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; +import java.lang.reflect.WildcardType; + +import org.eclipse.microprofile.faulttolerance.Fallback; + +/** + * Utility class to find a {@link Method} of a certain name with the same argument types as a given sample method as + * required to lookup the {@link Fallback}'s fallback method. + * + * @author Jan Bernitt + */ +public final class MethodLookupUtils { + + private MethodLookupUtils() { + // util + } + + public static Method findMethodWithMatchingNameAndArguments(String name, Method sample) { + Class currentType = sample.getDeclaringClass(); + while (currentType != Object.class) { + for (Method candidate : currentType.getDeclaredMethods()) { + if (name.equals(candidate.getName()) && isMatchingParameterList(sample, candidate)) { + return candidate; + } + } + for (Class implemented : currentType.getInterfaces()) { + for (Method candidate : implemented.getDeclaredMethods()) { + if (name.equals(candidate.getName()) && isMatchingParameterList(sample, candidate)) { + return candidate; + } + } + } + currentType = currentType.getSuperclass(); + } + return null; + } + + private static boolean isMatchingParameterList(Method sample, Method candidate) { + return isMatchtingParameterList(sample.getGenericParameterTypes(), candidate.getGenericParameterTypes()); + } + + private static boolean isMatchtingParameterList(Type[] samples, Type[] candidates) { + if (samples.length != candidates.length) { + return false; + } + for (int i = 0; i < samples.length; i++) { + if (!isMatchtingType(samples[i], candidates[i])) { + return false; + } + } + return true; + } + + private static boolean isMatchtingType(Type sample, Type candidate) { + return sample.equals(candidate) + || (candidate instanceof TypeVariable) + || (candidate instanceof GenericArrayType) + || isMatchingGenericType(sample, candidate) + || isMatchingWildcardType(sample, candidate); + } + + private static boolean isMatchingWildcardType(Type sample, Type candidate) { + if (sample instanceof WildcardType && candidate instanceof WildcardType) { + WildcardType sampleType = (WildcardType) sample; + WildcardType candidateType = (WildcardType) candidate; + return isMatchtingParameterList(sampleType.getLowerBounds(), candidateType.getLowerBounds()) + && isMatchtingParameterList(sampleType.getUpperBounds(), candidateType.getUpperBounds()); + } + return false; + } + + private static boolean isMatchingGenericType(Type sample, Type candidate) { + if (sample instanceof ParameterizedType && candidate instanceof ParameterizedType) { + ParameterizedType sampleType = (ParameterizedType) sample; + ParameterizedType candidateType = (ParameterizedType) candidate; + if (sampleType.getRawType() != candidateType.getRawType()) { + return false; + } + return isMatchtingParameterList(sampleType.getActualTypeArguments(), + candidateType.getActualTypeArguments()); + } + return false; + } +} diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/Policy.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/Policy.java index b5fb3085763..0e2b082500d 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/Policy.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/Policy.java @@ -45,19 +45,24 @@ public static void checkReturnsSameAs(Method annotatedMethod, Class valueType, String valueMethodName, Class... valueParameterTypes) { try { Method actual = valueType.getDeclaredMethod(valueMethodName, valueParameterTypes); - if (actual.getReturnType() != annotatedMethod.getReturnType()) { - throw new FaultToleranceDefinitionException(describe(annotatedMethod, annotationType, attribute) - + "whose return type of does not match."); - } + checkReturnsSameAs(annotatedMethod, annotationType, attribute, actual); } catch (NoSuchMethodException ex) { throw new FaultToleranceDefinitionException(describe(annotatedMethod, annotationType, attribute) + "refering to a method "+valueMethodName+" that does not exist for type: " + valueType.getName(), ex); } } + public static void checkReturnsSameAs(Method annotatedMethod, Class annotationType, + String attribute, Method value) { + if (value.getReturnType() != annotatedMethod.getReturnType()) { + throw new FaultToleranceDefinitionException(describe(annotatedMethod, annotationType, attribute) + + "whose return type of does not match."); + } + } + protected static String describe(Method annotatedMethod, Class annotationType, String attribute) { return "Method \"" + annotatedMethod.getName() + "\" in " + annotatedMethod.getDeclaringClass().getName() - + " annotated with " + annotationType.getCanonicalName() + + " annotated with " + annotationType.getSimpleName() + (attribute.isEmpty() ? " " : " has a " + attribute(attribute)); } diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/StaticAnalysisMethodContext.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/StaticAnalysisMethodContext.java new file mode 100644 index 00000000000..88bada99734 --- /dev/null +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/StaticAnalysisMethodContext.java @@ -0,0 +1,57 @@ +package fish.payara.microprofile.faulttolerance.policy; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; +import java.util.Map; + +import javax.interceptor.InvocationContext; + +final class StaticAnalysisMethodContext implements InvocationContext { + + private final Method annotated; + + public StaticAnalysisMethodContext(Method annotated) { + this.annotated = annotated; + } + + @Override + public Object getTarget() { + throw new UnsupportedOperationException(); + } + + @Override + public Object getTimer() { + throw new UnsupportedOperationException(); + } + + @Override + public Method getMethod() { + return annotated; + } + + @Override + public Constructor getConstructor() { + throw new UnsupportedOperationException(); + } + + @Override + public Object[] getParameters() { + throw new UnsupportedOperationException(); + } + + @Override + public void setParameters(Object[] params) { + throw new UnsupportedOperationException(); + } + + @Override + public Map getContextData() { + throw new UnsupportedOperationException(); + } + + @Override + public Object proceed() throws Exception { + throw new UnsupportedOperationException(); + } + +} diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/policy/FallbackMethodBean.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/policy/FallbackMethodBean.java new file mode 100644 index 00000000000..52651ab0a8a --- /dev/null +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/policy/FallbackMethodBean.java @@ -0,0 +1,19 @@ +package fish.payara.microprofile.faulttolerance.policy; + +@SuppressWarnings("unused") +public interface FallbackMethodBean { + + /* + * FallbackMethodDefaultMethodTest + */ + + default String fallbackMethodDefaultMethod_Fallback(int a, Long b) { + return "fallbackMethodDefaultMethod"; + } + + /* + * FallbackMethodInterfaceTest + */ + + public String fallbackMethodInterface_Fallback(int a, Long b); +} diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/policy/FallbackMethodBeanA.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/policy/FallbackMethodBeanA.java new file mode 100644 index 00000000000..6d8dc89597f --- /dev/null +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/policy/FallbackMethodBeanA.java @@ -0,0 +1,103 @@ +package fish.payara.microprofile.faulttolerance.policy; + +import static org.junit.Assert.fail; + +import java.lang.reflect.Method; +import java.util.List; +import java.util.Set; + +import org.eclipse.microprofile.faulttolerance.Fallback; + +@SuppressWarnings("unused") +public abstract class FallbackMethodBeanA extends FallbackMethodBeanB implements FallbackMethodBean { + + /* + * FallbackMethodAbstractTest + */ + + @Fallback(fallbackMethod = "fallbackMethodAbstract_Fallback") + public String fallbackMethodAbstract_Method(int a, Long b) { + throw new RuntimeException("fallbackMethodAbstract"); + } + + abstract protected String fallbackMethodAbstract_Fallback(int a, Long b); + + + /* + * FallbackMethodGenericAbstractTest + */ + + @Fallback(fallbackMethod = "fallbackMethodGenericAbstract_Fallback") + public String fallbackMethodGenericAbstract_Method(int a, L b) { + throw new RuntimeException("fallbackMethodGenericAbstract"); + } + + protected abstract String fallbackMethodGenericAbstract_Fallback(int a, L b); + + /* + * FallbackMethodGenericArrayTest + */ + + public String fallbackMethodGenericArray_Fallback(S[][] arg) { + return "fallbackMethodGenericArray"; + } + + /* + * FallbackMethodGenericComplexTest + */ + + public String fallbackMethodGenericComplex_Fallback(List> a) { + return "fallbackMethodGenericComplex"; + } + + /* + * FallbackMethodGenericTest + */ + + public String fallbackMethodGeneric_Fallback(int a, L b) { + return "fallbackMethodGeneric"; + } + + /* + * FallbackMethodGenericWildcardTest + */ + + public String fallbackMethodGenericWildcard_Fallback(List a) { + return "fallbackMethodGenericWildcard"; + } + + /* + * FallbackMethodInPackageTest + */ + + String fallbackMethodInPackage_Fallback(int a, Long b) { + return "fallbackMethodInPackage"; + } + + /* + * FallbackMethodInterfaceTest + */ + + @Fallback(fallbackMethod = "fallbackMethodInterface_Fallback") + public String fallbackMethodInterface_Method(int a, Long b) { + throw new RuntimeException("fallbackMethodInterface"); + } + + /* + * FallbackMethodSubclassOverrideTest + */ + + @Override + protected String fallbackMethodSubclassOverride_Fallback(int a, Long b) { + return "fallbackMethodSubclassOverride"; + } + + /* + * FallbackMethodSuperclassTest + */ + + protected String fallbackMethodSuperclass_Fallback(int a, Long b) { + return "fallbackMethodSuperclass"; + } + +} diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/policy/FallbackMethodBeanB.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/policy/FallbackMethodBeanB.java new file mode 100644 index 00000000000..254128e0a26 --- /dev/null +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/policy/FallbackMethodBeanB.java @@ -0,0 +1,78 @@ +package fish.payara.microprofile.faulttolerance.policy; + +import static org.junit.Assert.fail; + +import java.lang.reflect.Method; + +import org.eclipse.microprofile.faulttolerance.Fallback; + +import fish.payara.microprofile.faulttolerance.policy.sub.FallbackMethodBeanC; + +@SuppressWarnings("unused") +public class FallbackMethodBeanB extends FallbackMethodBeanC { + + /* + * FallbackMethodGenericDeepTest + */ + + public String fallbackMethodGenericDeep_Fallback(int a, L b) { + return "fallbackMethodGenericDeep"; + } + + /* + * FallbackMethodSubclassOverrideTest + */ + + @Fallback(fallbackMethod = "fallbackMethodSubclassOverride_Fallback") + public String fallbackMethodSubclassOverride_Method(int a, Long b) { + throw new RuntimeException("fallbackMethodSubclassOverride"); + } + + protected String fallbackMethodSubclassOverride_Fallback(int a, Long b) { + // This fallback method should not be called as it is overridden in subclass + return "Not this fallback"; + } + + /* + * FallbackMethodSubclassTest + */ + + @Fallback(fallbackMethod = "fallbackMethodSubclass_Fallback") + public String fallbackMethodSubclass_Method(int a, Long b) { + throw new RuntimeException("fallbackMethodSubclass"); + } + + /* + * FallbackMethodSuperclassPrivateTest + */ + + private String fallbackMethodSuperclassPrivate_Fallback(int a, Long b) { + return "fallbackMethodSuperclassPrivate"; + } + + /* + * Common Helper Methods + */ + static Object[] createNullArgumentsFor(Method method) { + Object[] args = new Object[method.getParameterCount()]; + for (int i = 0; i < method.getParameterCount(); i++) { + if (method.getParameterTypes()[i].isPrimitive()) { + args[i] = Integer.valueOf(0); + } + } + return args; + } + + static Method getMethod(Class target, String name) { + for (Method m : target.getDeclaredMethods()) { + if (name.equals(m.getName())) { + return m; + } + } + if (target.getSuperclass() != Object.class) { + return getMethod(target.getSuperclass(), name); + } + fail("Test setup failure: no method with name: "+name); + return null; + } +} diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/policy/FallbackMethodMissingTest.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/policy/FallbackMethodMissingTest.java new file mode 100644 index 00000000000..0f7c4c9e663 --- /dev/null +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/policy/FallbackMethodMissingTest.java @@ -0,0 +1,113 @@ +package fish.payara.microprofile.faulttolerance.policy; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.fail; + +import java.lang.reflect.Method; +import java.util.List; + +import org.eclipse.microprofile.faulttolerance.Fallback; +import org.eclipse.microprofile.faulttolerance.exceptions.FaultToleranceDefinitionException; +import org.junit.Test; + +/** + * Tests scenarios for invalid declarations as given in the TCK + * {@code org.eclipse.microprofile.fault.tolerance.tck.fallbackmethod} package. Names used are based on the TCK tests + * names. + * + * Test and annotated method are linked by naming conventions. Annotated method and its fallback method are linked by + * the {@link Fallback} annotation. All names are unique. + * + * These tests can not be run in TCK since they expect the {@link FaultToleranceDefinitionException} to be thrown + * unwrapped during bootstrap while weld does wrap these when thrown failing the TCK tests. + * + * @author Jan Bernitt + */ +@SuppressWarnings("unused") +public class FallbackMethodMissingTest extends FallbackMethodBeanB { + + /* + * FallbackMethodOutOfPackageTest + */ + + @Test + public void fallbackMethodOutOfPackage() { + assertMissingFallbackMethod( + "Method \"fallbackMethodOutOfPackage_Method\" in fish.payara.microprofile.faulttolerance.policy.FallbackMethodMissingTest annotated with Fallback has a fallbackMethod that refers to a method that is not accessible."); + } + + @Fallback(fallbackMethod = "fallbackMethodOutOfPackage_Fallback") + public String fallbackMethodOutOfPackage_Method(int a, Long b) { + throw new RuntimeException("fallbackMethodOutOfPackage"); + } + + /* + * FallbackMethodSubclassTest + */ + + @Test + public void fallbackMethodSubclass() { + assertMissingFallbackMethod( + "Fallback method \"fallbackMethodSubclass_Fallback\" not defined for fish.payara.microprofile.faulttolerance.policy.FallbackMethodBeanB"); + } + + public String fallbackMethodSubclass_Fallback(int a, Long b) { + return "fallbackMethodSubclass"; + } + + /* + * FallbackMethodSuperclassPrivateTest + */ + + @Test + public void fallbackMethodSuperclassPrivate() { + assertMissingFallbackMethod( + "Method \"fallbackMethodSuperclassPrivate_Method\" in fish.payara.microprofile.faulttolerance.policy.FallbackMethodMissingTest annotated with Fallback has a fallbackMethod that refers to a method that is not accessible."); + } + + @Fallback(fallbackMethod = "fallbackMethodSuperclassPrivate_Fallback") + public String fallbackMethodSuperclassPrivate_Method(int a, Long b) { + throw new RuntimeException("fallbackMethodSuperclassPrivate"); + } + + /* + * FallbackMethodWildcardNegativeTest + */ + + @Test + public void fallbackMethodWildcardNegative() { + assertMissingFallbackMethod( + "Fallback method \"fallbackMethodWildcardNegative_Fallback\" not defined for fish.payara.microprofile.faulttolerance.policy.FallbackMethodMissingTest"); + } + + @Fallback(fallbackMethod = "fallbackMethodWildcardNegative_Fallback") + public String fallbackMethodWildcardNegative_Method(List a) { + throw new RuntimeException("fallbackMethodWildcardNegative"); + } + + public String fallbackMethodWildcardNegative_Fallback(List b) { + return "fallbackMethodWildcardNegative"; + } + + /* + * Helper Methods + */ + + private void assertMissingFallbackMethod(String expectedErrorMessage) { + assertMissingFallbackMethod(getClass(), expectedErrorMessage); + } + + private void assertMissingFallbackMethod(Class target, String expectedErrorMessage) { + StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace(); + String testName = stackTraceElements[target == getClass() ? 3 : 2].getMethodName(); + Method annotatedMethod = getMethod(target, testName + "_Method"); + try { + FallbackPolicy policy = new FallbackPolicy(annotatedMethod, null, + annotatedMethod.getAnnotation(Fallback.class).fallbackMethod()); + fail("Fallback method should be invalid for " + annotatedMethod + " but got: " + policy); + } catch (FaultToleranceDefinitionException ex) { + assertEquals(expectedErrorMessage, ex.getMessage()); + } + } +} diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/policy/FallbackMethodTest.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/policy/FallbackMethodTest.java new file mode 100644 index 00000000000..ec57a5fe761 --- /dev/null +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/policy/FallbackMethodTest.java @@ -0,0 +1,291 @@ +package fish.payara.microprofile.faulttolerance.policy; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.fail; + +import java.lang.reflect.Method; +import java.util.List; +import java.util.Set; + +import org.eclipse.microprofile.faulttolerance.Fallback; +import org.eclipse.microprofile.faulttolerance.exceptions.FaultToleranceDefinitionException; +import org.junit.Test; + +/** + * Tests scenarios for valid declarations as given in the TCK + * {@code org.eclipse.microprofile.fault.tolerance.tck.fallbackmethod} package. Names used are based on the TCK tests + * names. + * + * Test and annotated method are linked by naming conventions. Annotated method and its fallback method are linked by + * the {@link Fallback} annotation. All names are unique. + * + * The fallback method lookup is non-trivial and debugging with the cycle of building, deploying and starting/stopping + * the app and run TCK simply become to time intensive to work out the correct behaviour. + * + * @author Jan Bernitt + */ +@SuppressWarnings("unused") +public class FallbackMethodTest extends FallbackMethodBeanA { + + /* + * FallbackMethodAbstractTest + */ + + @Test + public void fallbackMethodAbstract() { + assertFallbackMethod(); + } + + @Override + protected String fallbackMethodAbstract_Fallback(int a, Long b) { + return "fallbackMethodAbstract"; + } + + /* + * FallbackMethodBasicTest + */ + + @Test + public void fallbackMethodBasic() { + assertFallbackMethod(); + } + + @Fallback(fallbackMethod = "fallbackMethodBasic_Fallback") + public String fallbackMethodBasic_Method(int a, Long b) { + throw new RuntimeException("fallbackMethodBasic"); + } + + public String fallbackMethodBasic_Fallback(int a, Long b) { + return "fallbackMethodBasic"; + } + + /* + * FallbackMethodDefaultMethodTest + */ + + @Test + public void fallbackMethodDefaultMethod() { + assertFallbackMethod(); + } + + @Fallback(fallbackMethod = "fallbackMethodDefaultMethod_Fallback") + public String fallbackMethodDefaultMethod_Method(int a, Long b) { + throw new RuntimeException("fallbackMethodDefaultMethod"); + } + + /* + * FallbackMethodGenericAbstractTest + */ + + @Test + public void fallbackMethodGenericAbstract() { + assertFallbackMethod(); + } + + @Override + protected String fallbackMethodGenericAbstract_Fallback(int a, Long b) { + return "fallbackMethodGenericAbstract"; + } + + /* + * FallbackMethodGenericArrayTest + */ + + @Test + public void fallbackMethodGenericArray() { + assertFallbackMethod(); + } + + @Fallback(fallbackMethod = "fallbackMethodGenericArray_Fallback") + public String fallbackMethodGenericArray_Method(String[][] arg) { + throw new RuntimeException("fallbackMethodGenericArray"); + } + + /* + * FallbackMethodGenericComplexTest + */ + + @Test + public void fallbackMethodGenericComplex() { + assertFallbackMethod(); + } + + @Fallback(fallbackMethod = "fallbackMethodGenericComplex_Fallback") + public String fallbackMethodGenericComplex_Method(List> a) { + throw new RuntimeException("fallbackMethodGenericComplex"); + } + + /* + * FallbackMethodGenericDeepTest + */ + + @Test + public void fallbackMethodGenericDeep() { + assertFallbackMethod(); + } + + @Fallback(fallbackMethod = "fallbackMethodGenericDeep_Fallback") + public String fallbackMethodGenericDeep_Method(int a, Long b) { + throw new RuntimeException("fallbackMethodGenericDeep"); + } + + /* + * FallbackMethodGenericTest + */ + + @Test + public void fallbackMethodGeneric() { + assertFallbackMethod(); + } + + @Fallback(fallbackMethod = "fallbackMethodGeneric_Fallback") + public String fallbackMethodGeneric_Method(int a, Long b) { + throw new RuntimeException("fallbackMethodGeneric"); + } + + /* + * FallbackMethodGenericWildcardTest + */ + + @Test + public void fallbackMethodGenericWildcard() { + assertFallbackMethod(); + } + + @Fallback(fallbackMethod = "fallbackMethodGenericWildcard_Fallback") + public String fallbackMethodGenericWildcard_Method(List a) { + throw new RuntimeException("fallbackMethodGenericWildcard"); + } + + /* + * FallbackMethodInPackageTest + */ + + @Test + public void fallbackMethodInPackage() { + assertFallbackMethod(); + } + + @Fallback(fallbackMethod = "fallbackMethodInPackage_Fallback") + public String fallbackMethodInPackage_Method(int a, Long b) { + throw new RuntimeException("fallbackMethodInPackage"); + } + + /* + * FallbackMethodInterfaceTest + */ + + @Test + public void fallbackMethodInterface() { + assertFallbackMethod(FallbackMethodBeanA.class); + } + + @Override + public String fallbackMethodInterface_Fallback(int a, Long b) { + return "fallbackMethodInterface"; + } + + /* + * FallbackMethodPrivateTest + */ + + @Test + public void fallbackMethodPrivate() { + assertFallbackMethod(); + } + + @Fallback(fallbackMethod = "fallbackMethodPrivate_Fallback") + public String fallbackMethodPrivate_Method(int a, Long b) { + throw new RuntimeException("fallbackMethodPrivate"); + } + + private String fallbackMethodPrivate_Fallback(int a, Long b) { + return "fallbackMethodPrivate"; + } + + /* + * FallbackMethodSubclassOverrideTest + */ + + @Test + public void fallbackMethodSubclassOverride() { + assertFallbackMethod(); + } + + /* + * FallbackMethodSuperclassTest + */ + + @Test + public void fallbackMethodSuperclass() { + assertFallbackMethod(); + } + + @Fallback(fallbackMethod = "fallbackMethodSuperclass_Fallback") + public String fallbackMethodSuperclass_Method(int a, Long b) { + throw new RuntimeException("fallbackMethodSuperclass"); + } + + /* + * FallbackMethodVarargsTest + */ + + @Test + public void fallbackMethodVarargs() { + assertFallbackMethod(); + } + + @Fallback(fallbackMethod = "fallbackMethodVarargs_Fallback") + public String fallbackMethodVarargs_Method(int a, Long... b) { + throw new RuntimeException("fallbackMethodVarargs"); + } + + public String fallbackMethodVarargs_Fallback(int a, Long... b) { + return "fallbackMethodVarargs"; + } + + /* + * FallbackMethodWildcardTest + */ + + @Test + public void fallbackMethodWildcard() { + assertFallbackMethod(); + } + + @Fallback(fallbackMethod = "fallbackMethodWildcard_Fallback") + public String fallbackMethodWildcard_Method(List a) { + throw new RuntimeException("fallbackMethodWildcard"); + } + + public String fallbackMethodWildcard_Fallback(List b) { + return "fallbackMethodWildcard"; + } + + /* + * Helper Methods + */ + + private void assertFallbackMethod() { + assertFallbackMethod(getClass()); + } + + private void assertFallbackMethod(Class target) { + StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace(); + String testName = stackTraceElements[target == getClass() ? 3 : 2].getMethodName(); + Method annotatedMethod = getMethod(target, testName + "_Method"); + FallbackPolicy policy = new FallbackPolicy(annotatedMethod, null, + annotatedMethod.getAnnotation(Fallback.class).fallbackMethod()); + assertNotNull(policy); + assertNotNull(policy.fallbackMethod); + assertNotNull(policy.method); + try { + Object[] args = createNullArgumentsFor(annotatedMethod); + Object actual = policy.method.invoke(this, args); + assertEquals(testName, actual.toString()); + } catch (Exception e) { + throw new AssertionError("Failed to invoke fallback method", e); + } + } +} diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/policy/sub/FallbackMethodBeanC.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/policy/sub/FallbackMethodBeanC.java new file mode 100644 index 00000000000..b0d1596cecb --- /dev/null +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/policy/sub/FallbackMethodBeanC.java @@ -0,0 +1,13 @@ +package fish.payara.microprofile.faulttolerance.policy.sub; + +@SuppressWarnings("unused") +public class FallbackMethodBeanC { + + /* + * FallbackMethodOutOfPackageTest + */ + + String fallbackMethodOutOfPackage_Fallback(int a, Long b) { + return "fallbackMethodOutOfPackage"; + } +} From e41d816de598d992107e25ea797da3db9ff6a4fb Mon Sep 17 00:00:00 2001 From: Jan Bernitt Date: Tue, 16 Apr 2019 20:14:58 +0200 Subject: [PATCH 06/30] PAYARA-3468 more TCK tests as unit tests that cannot run, more uniform error messages --- .../faulttolerance/FaultToleranceConfig.java | 7 +- .../cdi/CdiFaultToleranceConfig.java | 25 +- .../interceptors/AsynchronousInterceptor.java | 2 +- .../BaseFaultToleranceInterceptor.java | 2 +- .../interceptors/BulkheadInterceptor.java | 2 +- .../CircuitBreakerInterceptor.java | 2 +- .../FaultToleranceInterceptor.java | 10 +- .../interceptors/RetryInterceptor.java | 2 +- .../interceptors/TimeoutInterceptor.java | 2 +- .../policy/AsynchronousPolicy.java | 9 +- .../faulttolerance/policy/FallbackPolicy.java | 13 +- .../policy/FaultTolerancePolicy.java | 17 +- .../faulttolerance/policy/Policy.java | 2 +- .../policy/AnnotationInvalidTest.java | 241 ++++++++++++++++++ .../policy/FallbackInvalidTest.java | 181 +++++++++++++ .../policy/FallbackMethodBeanB.java | 25 -- .../policy/FallbackMethodMissingTest.java | 113 -------- .../policy/FallbackMethodTest.java | 10 +- .../faulttolerance/test/TestUtils.java | 65 +++++ 19 files changed, 552 insertions(+), 178 deletions(-) create mode 100644 appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/policy/AnnotationInvalidTest.java create mode 100644 appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/policy/FallbackInvalidTest.java delete mode 100644 appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/policy/FallbackMethodMissingTest.java create mode 100644 appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/test/TestUtils.java diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/FaultToleranceConfig.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/FaultToleranceConfig.java index 82b928f7a5e..801863344f9 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/FaultToleranceConfig.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/FaultToleranceConfig.java @@ -4,6 +4,7 @@ import java.time.temporal.ChronoUnit; import javax.enterprise.inject.spi.BeanManager; +import javax.interceptor.Interceptor; import javax.interceptor.InvocationContext; import org.eclipse.microprofile.faulttolerance.Bulkhead; @@ -37,7 +38,7 @@ public interface FaultToleranceConfig { * General */ - default boolean isEnabled(InvocationContext context) { + default boolean isNonFallbackEnabled(InvocationContext context) { return true; } @@ -58,6 +59,10 @@ default boolean isAnnotationPresent(Class annotationType, return getAnnotation(annotationType, context) != null; } + default int interceptorPriority() { + return Interceptor.Priority.PLATFORM_AFTER + 15; + } + /* * Retry diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/cdi/CdiFaultToleranceConfig.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/cdi/CdiFaultToleranceConfig.java index e22a0186c4d..7be81c9f7d1 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/cdi/CdiFaultToleranceConfig.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/cdi/CdiFaultToleranceConfig.java @@ -5,10 +5,12 @@ import java.time.temporal.ChronoUnit; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import java.util.logging.Level; import java.util.logging.Logger; +import javax.interceptor.Interceptor; import javax.interceptor.InvocationContext; import org.eclipse.microprofile.config.Config; @@ -31,17 +33,19 @@ */ public class CdiFaultToleranceConfig implements FaultToleranceConfig, Serializable { - public static final String FAULT_TOLERANCE_ENABLED_PROPERTY = "MP_Fault_Tolerance_NonFallback_Enabled"; - public static final String METRICS_ENABLED_PROPERTY = "MP_Fault_Tolerance_Metrics_Enabled"; + private static final String NON_FALLBACK_ENABLED_PROPERTY = "MP_Fault_Tolerance_NonFallback_Enabled"; + private static final String METRICS_ENABLED_PROPERTY = "MP_Fault_Tolerance_Metrics_Enabled"; + private static final String INTERCEPTOR_PRIORITY_PROPERTY = "mp.fault.tolerance.interceptor.priority"; private static final Logger logger = Logger.getLogger(CdiFaultToleranceConfig.class.getName()); /** - * These two property should only be read once at the start of the application, therefore they are cached in static - * field. + * These tree properties should only be read once at the start of the application, therefore they are cached in + * static field. */ private final AtomicReference nonFallbackEnabled = new AtomicReference<>(); private final AtomicReference metricsEnabled = new AtomicReference<>(); + private final AtomicInteger interceptorPriority = new AtomicInteger(-1); private final Stereotypes sterotypes; private transient Config config; @@ -69,10 +73,10 @@ private Config getConfig() { */ @Override - public boolean isEnabled(InvocationContext context) { + public boolean isNonFallbackEnabled(InvocationContext context) { if (nonFallbackEnabled.get() == null) { nonFallbackEnabled.compareAndSet(null, - getConfig().getOptionalValue(FAULT_TOLERANCE_ENABLED_PROPERTY, Boolean.class).orElse(true)); + getConfig().getOptionalValue(NON_FALLBACK_ENABLED_PROPERTY, Boolean.class).orElse(true)); } return nonFallbackEnabled.get().booleanValue(); } @@ -89,7 +93,7 @@ public boolean isMetricsEnabled(InvocationContext context) { @Override public boolean isEnabled(Class annotationType, InvocationContext context) { return FaultToleranceCdiUtils.getEnabledOverrideValue(getConfig(), annotationType, context, - annotationType == Fallback.class || isEnabled(context)); + annotationType == Fallback.class || isNonFallbackEnabled(context)); } @Override @@ -97,6 +101,13 @@ public A getAnnotation(Class annotationType, Invocatio return FaultToleranceCdiUtils.getAnnotation(sterotypes, annotationType, context); } + @Override + public int interceptorPriority() { + return interceptorPriority.updateAndGet(priority -> priority > 0 ? priority + : getConfig().getOptionalValue(INTERCEPTOR_PRIORITY_PROPERTY, Integer.class) + .orElse(Interceptor.Priority.PLATFORM_AFTER + 15)); + } + /* * Retry */ diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/AsynchronousInterceptor.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/AsynchronousInterceptor.java index 34a715b77de..ce68c85ebca 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/AsynchronousInterceptor.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/AsynchronousInterceptor.java @@ -73,7 +73,7 @@ public Object intercept(InvocationContext context) throws Exception { try { // Attempt to proceed the InvocationContext with Asynchronous semantics if Fault Tolerance is enabled for // this method - if (getConfig().isEnabled(context) && getConfig().isEnabled(Asynchronous.class, context)) { + if (getConfig().isNonFallbackEnabled(context) && getConfig().isEnabled(Asynchronous.class, context)) { resultValue = asynchronous(context); } else { // If fault tolerance isn't enabled, just proceed as normal diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/BaseFaultToleranceInterceptor.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/BaseFaultToleranceInterceptor.java index a1c452cc994..f2151276f90 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/BaseFaultToleranceInterceptor.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/BaseFaultToleranceInterceptor.java @@ -56,7 +56,7 @@ public Object intercept(InvocationContext context) throws Exception { try { // Attempt to proceed the InvocationContext with FT semantics if FT is enabled for this method - if (getConfig().isEnabled(context) && getConfig().isEnabled(annotationType, context)) { + if (getConfig().isNonFallbackEnabled(context) && getConfig().isEnabled(annotationType, context)) { // Only increment the invocations metric if the Retry, Bulkhead, and CircuitBreaker annotations aren't present if (!getConfig().isAnnotationPresent(Bulkhead.class, context) && !getConfig().isAnnotationPresent(Retry.class, context) diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/BulkheadInterceptor.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/BulkheadInterceptor.java index b163f77ea0e..430a3bb578d 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/BulkheadInterceptor.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/BulkheadInterceptor.java @@ -77,7 +77,7 @@ public Object intercept(InvocationContext context) throws Exception { try { // Attempt to proceed the InvocationContext with Asynchronous semantics if Fault Tolerance is enabled for this // method - if (getConfig().isEnabled(context) && getConfig().isEnabled(Bulkhead.class, context)) { + if (getConfig().isNonFallbackEnabled(context) && getConfig().isEnabled(Bulkhead.class, context)) { if (getConfig().isMetricsEnabled(context)) { // Only increment the invocations metric if the Retry annotation isn't present if (getConfig().getAnnotation(Retry.class, context) == null) { diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/CircuitBreakerInterceptor.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/CircuitBreakerInterceptor.java index e4ee8f8b09b..72b7c0d95af 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/CircuitBreakerInterceptor.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/CircuitBreakerInterceptor.java @@ -76,7 +76,7 @@ public Object intercept(InvocationContext context) throws Exception { // Attempt to proceed the invocation with CircuitBreaker semantics if Fault Tolerance is enabled for this method try { // Attempt to proceed the InvocationContext with Asynchronous semantics if Fault Tolerance is enabled - if (getConfig().isEnabled(context) && getConfig().isEnabled(CircuitBreaker.class, context)) { + if (getConfig().isNonFallbackEnabled(context) && getConfig().isEnabled(CircuitBreaker.class, context)) { // Only increment the invocations metric if the Retry and Bulkhead annotations aren't present if (getConfig().getAnnotation(Bulkhead.class, context) == null && getConfig().getAnnotation(Retry.class, context) == null) { diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/FaultToleranceInterceptor.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/FaultToleranceInterceptor.java index 888cb7d4a7b..1f7fa69b6be 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/FaultToleranceInterceptor.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/FaultToleranceInterceptor.java @@ -8,6 +8,7 @@ import javax.annotation.Priority; import javax.enterprise.inject.spi.BeanManager; +import javax.enterprise.inject.spi.Prioritized; import javax.inject.Inject; import javax.interceptor.AroundInvoke; import javax.interceptor.Interceptor; @@ -22,8 +23,8 @@ @Interceptor @FaultToleranceBehaviour -@Priority(Interceptor.Priority.PLATFORM_AFTER) -public class FaultToleranceInterceptor implements Stereotypes, Serializable { +@Priority(Interceptor.Priority.PLATFORM_AFTER + 15) +public class FaultToleranceInterceptor implements Stereotypes, Serializable, Prioritized { private static final Logger logger = Logger.getLogger(FaultToleranceInterceptor.class.getName()); @@ -56,5 +57,10 @@ public boolean isStereotype(Class annotationType) { public Set getStereotypeDefinition(Class stereotype) { return beanManager.getStereotypeDefinition(stereotype); } + + @Override + public int getPriority() { + return Interceptor.Priority.PLATFORM_AFTER + 15; //TODO dynamic via config + } } diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/RetryInterceptor.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/RetryInterceptor.java index ac062509944..e90040a1298 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/RetryInterceptor.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/RetryInterceptor.java @@ -72,7 +72,7 @@ public Object intercept(InvocationContext context) throws Exception { try { // Attempt to proceed the InvocationContext with Asynchronous semantics if Fault Tolerance is enabled for this // method - if (getConfig().isEnabled(context) && getConfig().isEnabled(Retry.class, context)) { + if (getConfig().isNonFallbackEnabled(context) && getConfig().isEnabled(Retry.class, context)) { // Increment the invocations metric getMetrics().incrementInvocationsTotal(Retry.class, context); diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/TimeoutInterceptor.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/TimeoutInterceptor.java index c289488ee4b..9d973782a6e 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/TimeoutInterceptor.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/TimeoutInterceptor.java @@ -76,7 +76,7 @@ public Object intercept(InvocationContext context) throws Exception { try { // Attempt to proceed the InvocationContext with Asynchronous semantics if Fault Tolerance is enabled for this // method - if (getConfig().isEnabled(context) && getConfig().isEnabled(Timeout.class, context)) { + if (getConfig().isNonFallbackEnabled(context) && getConfig().isEnabled(Timeout.class, context)) { // Only increment the invocations metric if the Retry, Bulkhead, and CircuitBreaker annotations aren't present if (getConfig().getAnnotation(Bulkhead.class, context) == null && getConfig().getAnnotation(Retry.class, context) == null diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/AsynchronousPolicy.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/AsynchronousPolicy.java index 87096094e9d..5060a7e3b07 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/AsynchronousPolicy.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/AsynchronousPolicy.java @@ -1,5 +1,6 @@ package fish.payara.microprofile.faulttolerance.policy; +import java.lang.reflect.Method; import java.util.concurrent.CompletionStage; import java.util.concurrent.Future; @@ -23,16 +24,16 @@ private AsynchronousPolicy() { public static AsynchronousPolicy create(InvocationContext context, FaultToleranceConfig config) { if (config.isAnnotationPresent(Asynchronous.class, context) && config.isEnabled(Asynchronous.class, context)) { - checkReturnsFutureOrCompletionStage(context); + checkReturnsFutureOrCompletionStage(context.getMethod()); return IS_ASYNCHRONOUS; } return null; } - private static void checkReturnsFutureOrCompletionStage(InvocationContext context) { - Class returnType = context.getMethod().getReturnType(); + static void checkReturnsFutureOrCompletionStage(Method annotated) { + Class returnType = annotated.getReturnType(); if (returnType != Future.class && returnType != CompletionStage.class) { - throw new FaultToleranceDefinitionException(describe(context.getMethod(), Asynchronous.class, "") + throw new FaultToleranceDefinitionException(describe(annotated, Asynchronous.class, "") + "does not return a Future or CompletionStage but: " + returnType.getName()); } } diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/FallbackPolicy.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/FallbackPolicy.java index 1c8a4ad749f..f5fcc672ab5 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/FallbackPolicy.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/FallbackPolicy.java @@ -22,14 +22,14 @@ public final class FallbackPolicy extends Policy { public final Method method; public FallbackPolicy(Method annotated, Class> value, String fallbackMethod) { - checkUnambiguous(value, fallbackMethod); + checkUnambiguous(annotated, value, fallbackMethod); this.value = value; this.fallbackMethod = fallbackMethod; if (fallbackMethod != null && !fallbackMethod.isEmpty()) { method = MethodLookupUtils.findMethodWithMatchingNameAndArguments(fallbackMethod, annotated); if (method == null) { - throw new FaultToleranceDefinitionException("Fallback method \"" + fallbackMethod + "\" not defined for " - + annotated.getDeclaringClass().getName()); + throw new FaultToleranceDefinitionException(describe(annotated, Fallback.class, "fallbackMethod") + + "value referring to a method that is not defined or has a incompatible method signature."); } checkReturnsSameAs(annotated, Fallback.class, "fallbackMethod", method); checkAccessible(annotated, method); @@ -51,9 +51,10 @@ public static FallbackPolicy create(InvocationContext context, FaultToleranceCon return null; } - private static void checkUnambiguous(Class> value, String fallbackMethod) { + private static void checkUnambiguous(Method annotated, Class> value, String fallbackMethod) { if (fallbackMethod != null && !fallbackMethod.isEmpty() && value != null && value != Fallback.DEFAULT.class) { - throw new FaultToleranceDefinitionException("Both a fallback class and method have been set."); + throw new FaultToleranceDefinitionException( + describe(annotated, Fallback.class, "") + "defined both a fallback handler and a fallback method."); } } @@ -63,7 +64,7 @@ private static void checkAccessible(Method annotated, Method fallback) { if (Modifier.isPackage(fallback.getModifiers()) && !samePackage || Modifier.isPrivate(fallback.getModifiers()) && !sameClass) { throw new FaultToleranceDefinitionException(describe(annotated, Fallback.class, "fallbackMethod") - + "that refers to a method that is not accessible."); + + "value referring to a method that is not accessible."); } } diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/FaultTolerancePolicy.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/FaultTolerancePolicy.java index e574aa1e95c..eaf7e2192e5 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/FaultTolerancePolicy.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/FaultTolerancePolicy.java @@ -69,16 +69,15 @@ public static FaultTolerancePolicy asAnnotated(Method annotated) { */ public static FaultTolerancePolicy get(InvocationContext context, Supplier configSpplier) throws FaultToleranceDefinitionException { - return POLICY_BY_METHOD.compute(context.getMethod(), (method, policy) -> - policy != null && !policy.isExpired() ? policy : create(context, configSpplier)); + return POLICY_BY_METHOD.compute(context.getMethod(), + (method, policy) -> policy != null && !policy.isExpired() ? policy : create(context, configSpplier)); } private static FaultTolerancePolicy create(InvocationContext context, Supplier configSpplier) { FaultToleranceConfig config = configSpplier.get(); - boolean isFaultToleranceEnabled = config.isEnabled(context); - boolean metricsEnabled = config.isMetricsEnabled(context); - return new FaultTolerancePolicy(isFaultToleranceEnabled, - metricsEnabled, + return new FaultTolerancePolicy( + config.isNonFallbackEnabled(context), + config.isMetricsEnabled(context), AsynchronousPolicy.create(context, config), BulkheadPolicy.create(context, config), CircuitBreakerPolicy.create(context, config), @@ -89,7 +88,7 @@ private static FaultTolerancePolicy create(InvocationContext context, Supplier { + + /* + * FallbackMethodOutOfPackageTest + */ + + @Test + public void fallbackMethodOutOfPackage() { + assertAnnotationInvalid("Fallback has a fallbackMethod value referring to a method that is not accessible."); + } + + @Fallback(fallbackMethod = "fallbackMethodOutOfPackage_Fallback") + public String fallbackMethodOutOfPackage_Method(int a, Long b) { + throw new RuntimeException("fallbackMethodOutOfPackage"); + } + + /* + * FallbackMethodSubclassTest + */ + + @Test + public void fallbackMethodSubclass() { + assertAnnotationInvalid("Fallback has a fallbackMethod value referring to a method that is not defined or has a incompatible method signature."); + } + + public String fallbackMethodSubclass_Fallback(int a, Long b) { + return "fallbackMethodSubclass"; + } + + /* + * FallbackMethodSuperclassPrivateTest + */ + + @Test + public void fallbackMethodSuperclassPrivate() { + assertAnnotationInvalid("Fallback has a fallbackMethod value referring to a method that is not accessible."); + } + + @Fallback(fallbackMethod = "fallbackMethodSuperclassPrivate_Fallback") + public String fallbackMethodSuperclassPrivate_Method(int a, Long b) { + throw new RuntimeException("fallbackMethodSuperclassPrivate"); + } + + /* + * FallbackMethodWildcardNegativeTest + */ + + @Test + public void fallbackMethodWildcardNegative() { + assertAnnotationInvalid("Fallback has a fallbackMethod value referring to a method that is not defined or has a incompatible method signature."); + } + + @Fallback(fallbackMethod = "fallbackMethodWildcardNegative_Fallback") + public String fallbackMethodWildcardNegative_Method(List a) { + throw new RuntimeException("fallbackMethodWildcardNegative"); + } + + public String fallbackMethodWildcardNegative_Fallback(List b) { + return "fallbackMethodWildcardNegative"; + } + + /* + * IncompatibleFallbackMethodTest + */ + + @Test + public void fallbackMethodInvalidReturnType() { + assertAnnotationInvalid("Fallback has a fallbackMethod value whose return type of does not match."); + } + + @Fallback(fallbackMethod = "fallbackMethodInvalidReturnType") + public Integer fallbackMethodInvalidReturnType_Method() { + return 42; + } + + /** + * Fallback method with incompatible signature, different return type + * @return dummy string + */ + public String fallbackMethodInvalidReturnType_Fallback() { + return "fallbackMethodInvalidReturnType"; + } + + /* + * IncompatibleFallbackMethodWithArgsTest + */ + + @Test + public void fallbackMethodIncompatibleArgumentList() { + assertAnnotationInvalid("Fallback has a fallbackMethod value referring to a method that is not defined or has a incompatible method signature."); + } + + @Retry(maxRetries = 4) + @Fallback(fallbackMethod = "fallbackMethodIncompatibleArgumentList_Fallback") + public Integer fallbackMethodIncompatibleArgumentList_Method(String name, Integer type) { + return 42; + } + + /** + * Fallback method with incompatible signature, only one parameter + */ + public Integer fallbackMethodIncompatibleArgumentList_Fallback(String name) { + return 42; + } + + /* + * IncompatibleFallbackPolicies + */ + + public static class IncompatibleFallbackHandler implements FallbackHandler { + + @Override + public String handle(ExecutionContext context) { + return "fourty-two"; + } + + } + + @Test + public void fallbackMethodAndHandlerDefined() { + assertAnnotationInvalid("Fallback defined both a fallback handler and a fallback method."); + } + + @Fallback(value = IncompatibleFallbackHandler.class, fallbackMethod = "fallbackMethodAndHandlerDefined_Fallback") + public int fallbackMethodAndHandlerDefined_Method() { + return 42; + } + + public int fallbackMethodAndHandlerDefined_Fallback() { + return 22; + } + + /* + * IncompatibleFallbackTest + */ + + @Test + public void fallbackHandlerIncompatibleReturnType() { + assertAnnotationInvalid("Fallback has a value whose return type of does not match."); + } + + @Fallback(IncompatibleFallbackHandler.class) + public int fallbackHandlerIncompatibleReturnType_Method() { + return 42; + } + +} diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/policy/FallbackMethodBeanB.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/policy/FallbackMethodBeanB.java index 254128e0a26..cd67f8b278d 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/policy/FallbackMethodBeanB.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/policy/FallbackMethodBeanB.java @@ -50,29 +50,4 @@ private String fallbackMethodSuperclassPrivate_Fallback(int a, Long b) { return "fallbackMethodSuperclassPrivate"; } - /* - * Common Helper Methods - */ - static Object[] createNullArgumentsFor(Method method) { - Object[] args = new Object[method.getParameterCount()]; - for (int i = 0; i < method.getParameterCount(); i++) { - if (method.getParameterTypes()[i].isPrimitive()) { - args[i] = Integer.valueOf(0); - } - } - return args; - } - - static Method getMethod(Class target, String name) { - for (Method m : target.getDeclaredMethods()) { - if (name.equals(m.getName())) { - return m; - } - } - if (target.getSuperclass() != Object.class) { - return getMethod(target.getSuperclass(), name); - } - fail("Test setup failure: no method with name: "+name); - return null; - } } diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/policy/FallbackMethodMissingTest.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/policy/FallbackMethodMissingTest.java deleted file mode 100644 index 0f7c4c9e663..00000000000 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/policy/FallbackMethodMissingTest.java +++ /dev/null @@ -1,113 +0,0 @@ -package fish.payara.microprofile.faulttolerance.policy; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.fail; - -import java.lang.reflect.Method; -import java.util.List; - -import org.eclipse.microprofile.faulttolerance.Fallback; -import org.eclipse.microprofile.faulttolerance.exceptions.FaultToleranceDefinitionException; -import org.junit.Test; - -/** - * Tests scenarios for invalid declarations as given in the TCK - * {@code org.eclipse.microprofile.fault.tolerance.tck.fallbackmethod} package. Names used are based on the TCK tests - * names. - * - * Test and annotated method are linked by naming conventions. Annotated method and its fallback method are linked by - * the {@link Fallback} annotation. All names are unique. - * - * These tests can not be run in TCK since they expect the {@link FaultToleranceDefinitionException} to be thrown - * unwrapped during bootstrap while weld does wrap these when thrown failing the TCK tests. - * - * @author Jan Bernitt - */ -@SuppressWarnings("unused") -public class FallbackMethodMissingTest extends FallbackMethodBeanB { - - /* - * FallbackMethodOutOfPackageTest - */ - - @Test - public void fallbackMethodOutOfPackage() { - assertMissingFallbackMethod( - "Method \"fallbackMethodOutOfPackage_Method\" in fish.payara.microprofile.faulttolerance.policy.FallbackMethodMissingTest annotated with Fallback has a fallbackMethod that refers to a method that is not accessible."); - } - - @Fallback(fallbackMethod = "fallbackMethodOutOfPackage_Fallback") - public String fallbackMethodOutOfPackage_Method(int a, Long b) { - throw new RuntimeException("fallbackMethodOutOfPackage"); - } - - /* - * FallbackMethodSubclassTest - */ - - @Test - public void fallbackMethodSubclass() { - assertMissingFallbackMethod( - "Fallback method \"fallbackMethodSubclass_Fallback\" not defined for fish.payara.microprofile.faulttolerance.policy.FallbackMethodBeanB"); - } - - public String fallbackMethodSubclass_Fallback(int a, Long b) { - return "fallbackMethodSubclass"; - } - - /* - * FallbackMethodSuperclassPrivateTest - */ - - @Test - public void fallbackMethodSuperclassPrivate() { - assertMissingFallbackMethod( - "Method \"fallbackMethodSuperclassPrivate_Method\" in fish.payara.microprofile.faulttolerance.policy.FallbackMethodMissingTest annotated with Fallback has a fallbackMethod that refers to a method that is not accessible."); - } - - @Fallback(fallbackMethod = "fallbackMethodSuperclassPrivate_Fallback") - public String fallbackMethodSuperclassPrivate_Method(int a, Long b) { - throw new RuntimeException("fallbackMethodSuperclassPrivate"); - } - - /* - * FallbackMethodWildcardNegativeTest - */ - - @Test - public void fallbackMethodWildcardNegative() { - assertMissingFallbackMethod( - "Fallback method \"fallbackMethodWildcardNegative_Fallback\" not defined for fish.payara.microprofile.faulttolerance.policy.FallbackMethodMissingTest"); - } - - @Fallback(fallbackMethod = "fallbackMethodWildcardNegative_Fallback") - public String fallbackMethodWildcardNegative_Method(List a) { - throw new RuntimeException("fallbackMethodWildcardNegative"); - } - - public String fallbackMethodWildcardNegative_Fallback(List b) { - return "fallbackMethodWildcardNegative"; - } - - /* - * Helper Methods - */ - - private void assertMissingFallbackMethod(String expectedErrorMessage) { - assertMissingFallbackMethod(getClass(), expectedErrorMessage); - } - - private void assertMissingFallbackMethod(Class target, String expectedErrorMessage) { - StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace(); - String testName = stackTraceElements[target == getClass() ? 3 : 2].getMethodName(); - Method annotatedMethod = getMethod(target, testName + "_Method"); - try { - FallbackPolicy policy = new FallbackPolicy(annotatedMethod, null, - annotatedMethod.getAnnotation(Fallback.class).fallbackMethod()); - fail("Fallback method should be invalid for " + annotatedMethod + " but got: " + policy); - } catch (FaultToleranceDefinitionException ex) { - assertEquals(expectedErrorMessage, ex.getMessage()); - } - } -} diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/policy/FallbackMethodTest.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/policy/FallbackMethodTest.java index ec57a5fe761..553c4dd82c7 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/policy/FallbackMethodTest.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/policy/FallbackMethodTest.java @@ -12,6 +12,8 @@ import org.eclipse.microprofile.faulttolerance.exceptions.FaultToleranceDefinitionException; import org.junit.Test; +import fish.payara.microprofile.faulttolerance.test.TestUtils; + /** * Tests scenarios for valid declarations as given in the TCK * {@code org.eclipse.microprofile.fault.tolerance.tck.fallbackmethod} package. Names used are based on the TCK tests @@ -274,14 +276,14 @@ private void assertFallbackMethod() { private void assertFallbackMethod(Class target) { StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace(); String testName = stackTraceElements[target == getClass() ? 3 : 2].getMethodName(); - Method annotatedMethod = getMethod(target, testName + "_Method"); - FallbackPolicy policy = new FallbackPolicy(annotatedMethod, null, - annotatedMethod.getAnnotation(Fallback.class).fallbackMethod()); + Method annotatedMethod = TestUtils.getMethod(target, testName + "_Method"); + Fallback fallback = annotatedMethod.getAnnotation(Fallback.class); + FallbackPolicy policy = new FallbackPolicy(annotatedMethod, fallback.value(), fallback.fallbackMethod()); assertNotNull(policy); assertNotNull(policy.fallbackMethod); assertNotNull(policy.method); try { - Object[] args = createNullArgumentsFor(annotatedMethod); + Object[] args = TestUtils.createNullArgumentsFor(annotatedMethod); Object actual = policy.method.invoke(this, args); assertEquals(testName, actual.toString()); } catch (Exception e) { diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/test/TestUtils.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/test/TestUtils.java new file mode 100644 index 00000000000..6eecc87245e --- /dev/null +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/test/TestUtils.java @@ -0,0 +1,65 @@ +package fish.payara.microprofile.faulttolerance.test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.lang.reflect.Method; + +import org.eclipse.microprofile.faulttolerance.exceptions.FaultToleranceDefinitionException; + +import fish.payara.microprofile.faulttolerance.policy.FaultTolerancePolicy; + +public class TestUtils { + + public static Object[] createNullArgumentsFor(Method method) { + Object[] args = new Object[method.getParameterCount()]; + for (int i = 0; i < method.getParameterCount(); i++) { + if (method.getParameterTypes()[i].isPrimitive()) { + args[i] = Integer.valueOf(0); + } + } + return args; + } + + public static Method getMethod(Class target, String name) { + for (Method m : target.getDeclaredMethods()) { + if (name.equals(m.getName())) { + return m; + } + } + if (target.getSuperclass() != Object.class) { + return getMethod(target.getSuperclass(), name); + } + fail("Test setup failure: no method with name: "+name); + return null; + } + + public static void assertAnnotationInvalid(String expectedErrorMessage) { + try { + Method annotatedMethod = getAnnotatedMethod(); + FaultTolerancePolicy policy = FaultTolerancePolicy.asAnnotated(annotatedMethod); + fail("Annotation should be invalid for " + annotatedMethod + " but got: " + policy); + } catch (FaultToleranceDefinitionException ex) { + String message = ex.getMessage(); + int endGeneralPart = message.indexOf(" annotated with "); + assertTrue(endGeneralPart > 0); + assertEquals(expectedErrorMessage, message.substring(endGeneralPart + 16)); + } + } + + public static Method getAnnotatedMethod() { + StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace(); + int i = 0; + while (!stackTraceElements[i].getClassName().endsWith("Test")) { + i++; + } + StackTraceElement testMethodElement = stackTraceElements[i]; + String testName = testMethodElement.getMethodName(); + try { + return TestUtils.getMethod(Class.forName(testMethodElement.getClassName()), testName + "_Method"); + } catch (Exception e) { + throw new AssertionError("Failed to find annotated method in test class: ", e); + } + } +} From f13a746f420867c4a521e5a09750c6f110159db5 Mon Sep 17 00:00:00 2001 From: Jan Bernitt Date: Wed, 17 Apr 2019 11:48:46 +0200 Subject: [PATCH 07/30] PAYARA-3468 FIXED policy per target method (not only method) --- .../faulttolerance/policy/FaultTolerancePolicy.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/FaultTolerancePolicy.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/FaultTolerancePolicy.java index eaf7e2192e5..850f08f14c5 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/FaultTolerancePolicy.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/FaultTolerancePolicy.java @@ -47,11 +47,12 @@ public final class FaultTolerancePolicy implements Serializable { private static final long TTL = 60 * 1000; - private static final ConcurrentHashMap POLICY_BY_METHOD = new ConcurrentHashMap<>(); + private static final ConcurrentHashMap, ConcurrentHashMap> POLICY_BY_METHOD = new ConcurrentHashMap<>(); public static void clean() { long now = System.currentTimeMillis(); - POLICY_BY_METHOD.entrySet().removeIf(entry -> now > entry.getValue().expiresMillis); + POLICY_BY_METHOD.forEachValue(Long.MAX_VALUE, + map -> map.entrySet().removeIf(entry -> now > entry.getValue().expiresMillis)); } public static FaultTolerancePolicy asAnnotated(Method annotated) { @@ -69,8 +70,9 @@ public static FaultTolerancePolicy asAnnotated(Method annotated) { */ public static FaultTolerancePolicy get(InvocationContext context, Supplier configSpplier) throws FaultToleranceDefinitionException { - return POLICY_BY_METHOD.compute(context.getMethod(), - (method, policy) -> policy != null && !policy.isExpired() ? policy : create(context, configSpplier)); + return POLICY_BY_METHOD.computeIfAbsent(context.getTarget().getClass(), target -> new ConcurrentHashMap<>()) + .compute(context.getMethod(), (method, policy) -> + policy != null && !policy.isExpired() ? policy : create(context, configSpplier)); } private static FaultTolerancePolicy create(InvocationContext context, Supplier configSpplier) { From f5a20c3fe0fe1b0ff799443e761736ebed73f630 Mon Sep 17 00:00:00 2001 From: Jan Bernitt Date: Wed, 17 Apr 2019 20:28:47 +0200 Subject: [PATCH 08/30] PAYARA-3468 added metrics (mostly correct), fixed Future fault behaviour --- .../faulttolerance/FaultToleranceMetrics.java | 73 ++++---- .../faulttolerance/FaultToleranceService.java | 5 +- .../cdi/CdiFaultToleranceMetrics.java | 21 +-- .../cdi/FaultToleranceCdiUtils.java | 11 +- .../BaseFaultToleranceInterceptor.java | 4 +- .../interceptors/BulkheadInterceptor.java | 4 +- .../CircuitBreakerInterceptor.java | 12 +- .../interceptors/RetryInterceptor.java | 4 +- .../interceptors/TimeoutInterceptor.java | 4 +- .../interceptors/fallback/FallbackPolicy.java | 2 +- .../policy/AsynchronousPolicy.java | 9 +- .../policy/FaultTolerancePolicy.java | 166 +++++++++++++----- .../faulttolerance/policy/RetryPolicy.java | 4 + .../state/CircuitBreakerState.java | 10 +- 14 files changed, 203 insertions(+), 126 deletions(-) diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/FaultToleranceMetrics.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/FaultToleranceMetrics.java index 161ef094c3a..5f1c1097cc9 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/FaultToleranceMetrics.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/FaultToleranceMetrics.java @@ -1,16 +1,9 @@ package fish.payara.microprofile.faulttolerance; -import java.lang.annotation.Annotation; import java.util.function.LongSupplier; import javax.interceptor.InvocationContext; -import org.eclipse.microprofile.faulttolerance.Bulkhead; -import org.eclipse.microprofile.faulttolerance.CircuitBreaker; -import org.eclipse.microprofile.faulttolerance.Fallback; -import org.eclipse.microprofile.faulttolerance.Retry; -import org.eclipse.microprofile.faulttolerance.Timeout; - /** * Encodes the specifics of the FT metrics names using default methods while decoupling * {@link org.eclipse.microprofile.metrics.MetricRegistry}. @@ -19,8 +12,10 @@ */ public interface FaultToleranceMetrics { + FaultToleranceMetrics DISABLED = new FaultToleranceMetrics() { /* does nothing */ }; + /* - * Generic (to be implemented) + * Generic (to be implemented/overridden) */ /** @@ -30,7 +25,9 @@ public interface FaultToleranceMetrics { * @param annotationType * @param context */ - void increment(String metric, Class annotationType, InvocationContext context); + default void increment(String metric, InvocationContext context) { + //NOOP (used when metrics are disabled) + } /** * Histogram: @@ -40,7 +37,9 @@ public interface FaultToleranceMetrics { * @param annotationType * @param context */ - void add(String metric, long nanos, Class annotationType, InvocationContext context); + default void add(String metric, long nanos, InvocationContext context) { + //NOOP (used when metrics are disabled) + } /** * Gauge: @@ -50,19 +49,21 @@ public interface FaultToleranceMetrics { * @param annotationType * @param context */ - void insert(String metric, LongSupplier gauge, Class annotationType, InvocationContext context); + default void insert(String metric, LongSupplier gauge, InvocationContext context) { + //NOOP (used when metrics are disabled) + } /* * @Retry, @Timeout, @CircuitBreaker, @Bulkhead and @Fallback */ - default void incrementInvocationsTotal(Class annotationType, InvocationContext context) { - increment("ft.%s.invocations.total", annotationType, context); + default void incrementInvocationsTotal(InvocationContext context) { + increment("ft.%s.invocations.total", context); } - default void incrementInvocationsFailedTotal(Class annotationType, InvocationContext context) { - increment("ft.%s.invocations.failed.total", annotationType, context); + default void incrementInvocationsFailedTotal(InvocationContext context) { + increment("ft.%s.invocations.failed.total", context); } @@ -71,19 +72,19 @@ default void incrementInvocationsFailedTotal(Class annotat */ default void incrementRetryCallsSucceededNotRetriedTotal(InvocationContext context) { - increment("ft.%s.retry.callsSucceededNotRetried.total", Retry.class, context); + increment("ft.%s.retry.callsSucceededNotRetried.total", context); } default void incrementRetryCallsSucceededRetriedTotal(InvocationContext context) { - increment("ft.%s.retry.callsSucceededRetried.total", Retry.class, context); + increment("ft.%s.retry.callsSucceededRetried.total", context); } default void incrementRetryCallsFailedTotal(InvocationContext context) { - increment("ft.%s.retry.callsFailed.total", Retry.class, context); + increment("ft.%s.retry.callsFailed.total", context); } default void incrementRetryRetriesTotal(InvocationContext context) { - increment("ft.%s.retry.retries.total", Retry.class, context); + increment("ft.%s.retry.retries.total", context); } @@ -92,15 +93,15 @@ default void incrementRetryRetriesTotal(InvocationContext context) { */ default void addTimeoutExecutionDuration(long duration, InvocationContext context) { - add("ft.%s.timeout.executionDuration", duration, Timeout.class, context); + add("ft.%s.timeout.executionDuration", duration, context); } default void incrementTimeoutCallsTimedOutTotal(InvocationContext context) { - increment("ft.%s.timeout.callsTimedOut.total", Timeout.class, context); + increment("ft.%s.timeout.callsTimedOut.total", context); } default void incrementTimeoutCallsNotTimedOutTotal(InvocationContext context) { - increment("ft.%s.timeout.callsNotTimedOut.total", Timeout.class, context); + increment("ft.%s.timeout.callsNotTimedOut.total", context); } @@ -109,31 +110,31 @@ default void incrementTimeoutCallsNotTimedOutTotal(InvocationContext context) { */ default void incrementCircuitbreakerCallsSucceededTotal(InvocationContext context) { - increment("ft.%s.circuitbreaker.callsSucceeded.total", CircuitBreaker.class, context); + increment("ft.%s.circuitbreaker.callsSucceeded.total", context); } default void incrementCircuitbreakerCallsFailedTotal(InvocationContext context) { - increment("ft.%s.circuitbreaker.callsFailed.total", CircuitBreaker.class, context); + increment("ft.%s.circuitbreaker.callsFailed.total", context); } default void incrementCircuitbreakerCallsPreventedTotal(InvocationContext context) { - increment("ft.%s.circuitbreaker.callsPrevented.total", CircuitBreaker.class, context); + increment("ft.%s.circuitbreaker.callsPrevented.total", context); } default void incrementCircuitbreakerOpenedTotal(InvocationContext context) { - increment("ft.%s.circuitbreaker.opened.total", CircuitBreaker.class, context); + increment("ft.%s.circuitbreaker.opened.total", context); } default void insertCircuitbreakerOpenTotal(LongSupplier gauge, InvocationContext context) { - insert("ft.%s.circuitbreaker.open.total", gauge, CircuitBreaker.class, context); + insert("ft.%s.circuitbreaker.open.total", gauge, context); } default void insertCircuitbreakerHalfOpenTotal(LongSupplier gauge, InvocationContext context) { - insert("ft.%s.circuitbreaker.halfOpen.total", gauge, CircuitBreaker.class, context); + insert("ft.%s.circuitbreaker.halfOpen.total", gauge, context); } default void insertCircuitbreakerClosedTotal(LongSupplier gauge, InvocationContext context) { - insert("ft.%s.circuitbreaker.closed.total", gauge, CircuitBreaker.class, context); + insert("ft.%s.circuitbreaker.closed.total", gauge, context); } @@ -142,27 +143,27 @@ default void insertCircuitbreakerClosedTotal(LongSupplier gauge, InvocationConte */ default void incrementBulkheadCallsAcceptedTotal(InvocationContext context) { - increment("ft.%s.bulkhead.callsAccepted.total", Bulkhead.class, context); + increment("ft.%s.bulkhead.callsAccepted.total", context); } default void incrementBulkheadCallsRejectedTotal(InvocationContext context) { - increment("ft.%s.bulkhead.callsRejected.total", Bulkhead.class, context); + increment("ft.%s.bulkhead.callsRejected.total", context); } default void insertBulkheadConcurrentExecutions(LongSupplier gauge, InvocationContext context) { - insert("ft.%s.bulkhead.concurrentExecutions", gauge, Bulkhead.class, context); + insert("ft.%s.bulkhead.concurrentExecutions", gauge, context); } default void insertBulkheadWaitingQueuePopulation(LongSupplier gauge, InvocationContext context) { - insert("ft.%s.bulkhead.waitingQueue.population", gauge, Bulkhead.class, context); + insert("ft.%s.bulkhead.waitingQueue.population", gauge, context); } default void addBulkheadExecutionDuration(long duration, InvocationContext context) { - add("ft.%s.bulkhead.executionDuration", duration, Bulkhead.class, context); + add("ft.%s.bulkhead.executionDuration", duration, context); } default void addBulkheadWaitingDuration(long duration, InvocationContext context) { - add("ft.%s.bulkhead.waiting.duration", duration, Bulkhead.class, context); + add("ft.%s.bulkhead.waiting.duration", duration, context); } @@ -171,6 +172,6 @@ default void addBulkheadWaitingDuration(long duration, InvocationContext context */ default void incrementFallbackCallsTotal(InvocationContext context) { - increment("ft.%s.fallback.calls.total", Fallback.class, context); + increment("ft.%s.fallback.calls.total", context); } } diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/FaultToleranceService.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/FaultToleranceService.java index 78df876aebf..0b7075dd32e 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/FaultToleranceService.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/FaultToleranceService.java @@ -327,10 +327,9 @@ public void runAsynchronous(CompletableFuture asyncResult, Callable annotationType, - InvocationContext context) { - getMetricRegistry().counter(metricName(keyPattern, annotationType, context)).inc(); + public void increment(String keyPattern, InvocationContext context) { + getMetricRegistry().counter(metricName(keyPattern, context)).inc(); } @Override - public void add(String keyPattern, long duration, Class annotationType, - InvocationContext context) { - getMetricRegistry().histogram(metricName(keyPattern, annotationType, context)).update(duration); + public void add(String keyPattern, long duration, InvocationContext context) { + getMetricRegistry().histogram(metricName(keyPattern, context)).update(duration); } @Override - public void insert(String keyPattern, LongSupplier gauge, Class annotationType, - InvocationContext context) { - String metricName = metricName(keyPattern, annotationType, context); + public void insert(String keyPattern, LongSupplier gauge, InvocationContext context) { + String metricName = metricName(keyPattern, context); Gauge existingGauge = getMetricRegistry().getGauges().get(metricName); if (existingGauge == null) { Gauge newGauge = gauge::getAsLong; @@ -47,9 +43,8 @@ public void insert(String keyPattern, LongSupplier gauge, Class annotationType, - InvocationContext context) { - return String.format(keyPattern, FaultToleranceCdiUtils.getFullAnnotatedMethodSignature(context, annotationType)); + private static String metricName(String keyPattern, InvocationContext context) { + return String.format(keyPattern, FaultToleranceCdiUtils.getCanonicalMethodName(context)); } private MetricRegistry getMetricRegistry() { diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/cdi/FaultToleranceCdiUtils.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/cdi/FaultToleranceCdiUtils.java index 0f34ca94177..2fbc9653223 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/cdi/FaultToleranceCdiUtils.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/cdi/FaultToleranceCdiUtils.java @@ -248,14 +248,11 @@ public static String getPlainCanonicalName(Class type) /** * Gets the full method signature from an invocation context, stripping out any Weld proxy name gubbins. - * @param The class of the annotation + * * @param context The context of the method invocation - * @param annotationClass The class of the annotation - * @return + * @return full canonical name of the method as in {@code my.pack.MyClass.myMethod} */ - public static String getFullAnnotatedMethodSignature(InvocationContext context, - Class annotationClass) { - return getPlainCanonicalName(getAnnotatedMethodClass(context, annotationClass)) + "." - + context.getMethod().getName(); + public static String getCanonicalMethodName(InvocationContext context) { + return getPlainCanonicalName(context.getMethod().getDeclaringClass()) + "." + context.getMethod().getName(); } } diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/BaseFaultToleranceInterceptor.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/BaseFaultToleranceInterceptor.java index f2151276f90..2d7f9dd2269 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/BaseFaultToleranceInterceptor.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/BaseFaultToleranceInterceptor.java @@ -61,7 +61,7 @@ public Object intercept(InvocationContext context) throws Exception { if (!getConfig().isAnnotationPresent(Bulkhead.class, context) && !getConfig().isAnnotationPresent(Retry.class, context) && !getConfig().isAnnotationPresent(CircuitBreaker.class, context)) { - getMetrics().incrementInvocationsTotal(annotationType, context); + getMetrics().incrementInvocationsTotal(context); } logger.log(Level.FINER, "Proceeding invocation with " + type + " semantics"); @@ -87,7 +87,7 @@ public Object intercept(InvocationContext context) throws Exception { resultValue = fallbackPolicy.fallback(context, ex); } else { // Increment the failure counter metric - getMetrics().incrementInvocationsFailedTotal(annotationType, context); + getMetrics().incrementInvocationsFailedTotal(context); throw ex; } } diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/BulkheadInterceptor.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/BulkheadInterceptor.java index 430a3bb578d..f1fd7183576 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/BulkheadInterceptor.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/BulkheadInterceptor.java @@ -81,7 +81,7 @@ public Object intercept(InvocationContext context) throws Exception { if (getConfig().isMetricsEnabled(context)) { // Only increment the invocations metric if the Retry annotation isn't present if (getConfig().getAnnotation(Retry.class, context) == null) { - getMetrics().incrementInvocationsTotal(Bulkhead.class, context); + getMetrics().incrementInvocationsTotal(context); } } @@ -114,7 +114,7 @@ public Object intercept(InvocationContext context) throws Exception { logger.log(Level.FINE, "Fallback annotation not found on method, propagating error upwards.", ex); // Increment the failure counter metric - getMetrics().incrementInvocationsFailedTotal(Bulkhead.class, context); + getMetrics().incrementInvocationsFailedTotal(context); throw ex; } } diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/CircuitBreakerInterceptor.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/CircuitBreakerInterceptor.java index 72b7c0d95af..0ce9b5c96c1 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/CircuitBreakerInterceptor.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/CircuitBreakerInterceptor.java @@ -80,7 +80,7 @@ public Object intercept(InvocationContext context) throws Exception { // Only increment the invocations metric if the Retry and Bulkhead annotations aren't present if (getConfig().getAnnotation(Bulkhead.class, context) == null && getConfig().getAnnotation(Retry.class, context) == null) { - getMetrics().incrementInvocationsTotal(CircuitBreaker.class, context); + getMetrics().incrementInvocationsTotal(context); } logger.log(Level.FINER, "Proceeding invocation with circuitbreaker semantics"); @@ -107,7 +107,7 @@ && getConfig().getAnnotation(Retry.class, context) == null) { resultValue = fallbackPolicy.fallback(context, ex); } else { // Increment the failure counter metric - getMetrics().incrementInvocationsFailedTotal(CircuitBreaker.class, context); + getMetrics().incrementInvocationsFailedTotal(context); throw ex; } } @@ -132,9 +132,9 @@ private Object circuitBreak(InvocationContext context) throws Exception { CircuitBreakerState circuitBreakerState = getExecution().getState(requestVolumeThreshold, context); if (getConfig().isMetricsEnabled(context)) { - getMetrics().insertCircuitbreakerOpenTotal(circuitBreakerState::isOpen, context); - getMetrics().insertCircuitbreakerHalfOpenTotal(circuitBreakerState::isHalfOpen, context); - getMetrics().insertCircuitbreakerClosedTotal(circuitBreakerState::isClosed, context); + getMetrics().insertCircuitbreakerOpenTotal(circuitBreakerState::nanosOpen, context); + getMetrics().insertCircuitbreakerHalfOpenTotal(circuitBreakerState::nanosHalfOpen, context); + getMetrics().insertCircuitbreakerClosedTotal(circuitBreakerState::nanosClosed, context); } switch (circuitBreakerState.getCircuitState()) { @@ -270,7 +270,7 @@ private boolean shouldFail(Class[] failOn, Exception ex) { private void breakCircuitIfRequired(long failureThreshold, CircuitBreakerState circuitBreakerState, long delayMillis, InvocationContext context) throws Exception { // If we're over the failure threshold, open the circuit - if (circuitBreakerState.isOverFailureThreshold(failureThreshold)) { + if (circuitBreakerState.isOverFailureThreshold(0, 0d)) { logger.log(Level.FINE, "CircuitBreaker is over failure threshold {0}, opening circuit", failureThreshold); diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/RetryInterceptor.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/RetryInterceptor.java index e90040a1298..4bb3bb1c699 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/RetryInterceptor.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/RetryInterceptor.java @@ -74,7 +74,7 @@ public Object intercept(InvocationContext context) throws Exception { // method if (getConfig().isNonFallbackEnabled(context) && getConfig().isEnabled(Retry.class, context)) { // Increment the invocations metric - getMetrics().incrementInvocationsTotal(Retry.class, context); + getMetrics().incrementInvocationsTotal(context); logger.log(Level.FINER, "Proceeding invocation with retry semantics"); resultValue = retry(context); @@ -93,7 +93,7 @@ public Object intercept(InvocationContext context) throws Exception { resultValue = fallbackPolicy.fallback(context, ex); } else { // Increment the failure counter metric - getMetrics().incrementInvocationsFailedTotal(Retry.class, context); + getMetrics().incrementInvocationsFailedTotal(context); throw ex; } } diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/TimeoutInterceptor.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/TimeoutInterceptor.java index 9d973782a6e..b07a82cf084 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/TimeoutInterceptor.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/TimeoutInterceptor.java @@ -81,7 +81,7 @@ public Object intercept(InvocationContext context) throws Exception { if (getConfig().getAnnotation(Bulkhead.class, context) == null && getConfig().getAnnotation(Retry.class, context) == null && getConfig().getAnnotation(CircuitBreaker.class, context) == null) { - getMetrics().incrementInvocationsTotal(Timeout.class, context); + getMetrics().incrementInvocationsTotal(context); } logger.log(Level.FINER, "Proceeding invocation with timeout semantics"); @@ -108,7 +108,7 @@ && getConfig().getAnnotation(CircuitBreaker.class, context) == null) { resultValue = fallbackPolicy.fallback(context, ex); } else { // Increment the failure counter metric - getMetrics().incrementInvocationsFailedTotal(Timeout.class, context); + getMetrics().incrementInvocationsFailedTotal(context); throw ex; } } diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/fallback/FallbackPolicy.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/fallback/FallbackPolicy.java index ac3b8504a69..32c049fd6ab 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/fallback/FallbackPolicy.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/fallback/FallbackPolicy.java @@ -103,7 +103,7 @@ public Object fallback(InvocationContext context, Throwable exception) throws Ex metrics.incrementFallbackCallsTotal(context); } catch (Exception ex) { // Increment the failure counter metric - metrics.incrementInvocationsFailedTotal(Fallback.class, context); + metrics.incrementInvocationsFailedTotal(context); throw ex; } finally { execution.endTrace(); diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/AsynchronousPolicy.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/AsynchronousPolicy.java index 5060a7e3b07..5f94d95462d 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/AsynchronousPolicy.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/AsynchronousPolicy.java @@ -16,7 +16,8 @@ */ public final class AsynchronousPolicy extends Policy { - private static final AsynchronousPolicy IS_ASYNCHRONOUS = new AsynchronousPolicy(); + private static final AsynchronousPolicy FUTURE = new AsynchronousPolicy(); + private static final AsynchronousPolicy COMPLETION_STAGE = new AsynchronousPolicy(); private AsynchronousPolicy() { // hide @@ -25,7 +26,7 @@ private AsynchronousPolicy() { public static AsynchronousPolicy create(InvocationContext context, FaultToleranceConfig config) { if (config.isAnnotationPresent(Asynchronous.class, context) && config.isEnabled(Asynchronous.class, context)) { checkReturnsFutureOrCompletionStage(context.getMethod()); - return IS_ASYNCHRONOUS; + return context.getMethod().getReturnType() == Future.class ? FUTURE : COMPLETION_STAGE; } return null; } @@ -43,4 +44,8 @@ public static Future toFuture(Object asyncResult) { ? ((CompletionStage) asyncResult).toCompletableFuture() : (Future) asyncResult; } + + public boolean isSuccessWhenCompletedExceptionally() { + return this == FUTURE; + } } diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/FaultTolerancePolicy.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/FaultTolerancePolicy.java index 850f08f14c5..570245d1b80 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/FaultTolerancePolicy.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/FaultTolerancePolicy.java @@ -4,7 +4,9 @@ import java.lang.reflect.Method; import java.time.Duration; import java.util.Set; +import java.util.concurrent.Callable; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; @@ -143,10 +145,6 @@ public boolean isTimeoutPresent() { return timeout != null; } - interface InvocationStage { - Object run(FaultToleranceInvocation invocation) throws Exception; - } - static final class FaultToleranceInvocation { final InvocationContext context; final FaultToleranceExecution execution; @@ -163,12 +161,12 @@ static final class FaultToleranceInvocation { this.asyncWorkers = asyncWorkers; } - Object runStageWithWorker(InvocationStage stage) throws Exception { + Object runStageWithWorker(Callable stage) throws Exception { timeoutIfConcludedConcurrently(); Thread current = Thread.currentThread(); asyncWorkers.add(current); try { - return stage.run(this); + return stage.call(); } finally { asyncWorkers.remove(current); } @@ -185,14 +183,23 @@ public Object proceed(InvocationContext context, FaultToleranceExecution executi if (!isPresent) { return context.proceed(); } - return processAsynchronousStage(context, execution); + FaultToleranceMetrics metrics = isMetricsEnabled + ? execution.getMetrics(context) + : FaultToleranceMetrics.DISABLED; + try { + metrics.incrementInvocationsTotal(context); + return processAsynchronousStage(context, execution, metrics); + } catch (Exception e) { + metrics.incrementInvocationsFailedTotal(context); + throw e; + } } /** * Stage that takes care of the {@link AsynchronousPolicy} handling. */ - private Object processAsynchronousStage(InvocationContext context, FaultToleranceExecution execution) throws Exception { - FaultToleranceMetrics metrics = isMetricsEnabled ? execution.getMetrics(context) : null; + private Object processAsynchronousStage(InvocationContext context, FaultToleranceExecution execution, + FaultToleranceMetrics metrics) throws Exception { if (!isAsynchronous()) { return processFallbackStage(new FaultToleranceInvocation(context, execution, metrics, null, null)); } @@ -209,10 +216,26 @@ public boolean cancel(boolean mayInterruptIfRunning) { } return false; } + + /** + * Note that the exception is expected to be the exception thrown when trying to resolve the future returned + * by the annotated method. + */ + @Override + public boolean completeExceptionally(Throwable ex) { + if (ex instanceof ExecutionException) { + metrics.incrementInvocationsFailedTotal(context); + return super.completeExceptionally(ex.getCause()); + } else if (!asynchronous.isSuccessWhenCompletedExceptionally()) { + metrics.incrementInvocationsFailedTotal(context); + } + return super.completeExceptionally(ex); + } }; FaultToleranceInvocation invocation = new FaultToleranceInvocation(context, execution, metrics, asyncResult, asyncWorkers); - execution.runAsynchronous(asyncResult, () -> invocation.runStageWithWorker(this::processFallbackStage)); + execution.runAsynchronous(asyncResult, + () -> invocation.runStageWithWorker(() -> processFallbackStage(invocation))); return asyncResult; } @@ -226,35 +249,47 @@ private Object processFallbackStage(FaultToleranceInvocation invocation) throws try { return processRetryStage(invocation); } catch (Exception ex) { - return processFallbackException(invocation, ex); - } - } - - private Object processFallbackException(FaultToleranceInvocation invocation, Exception ex) throws Exception { - if (fallback.isHandlerPresent()) { - return invocation.execution.fallbackHandle(fallback.value, invocation.context, ex); + invocation.metrics.incrementFallbackCallsTotal(invocation.context); + if (fallback.isHandlerPresent()) { + return invocation.execution.fallbackHandle(fallback.value, invocation.context, ex); + } + return invocation.execution.fallbackInvoke(fallback.method, invocation.context); } - return invocation.execution.fallbackInvoke(fallback.method, invocation.context); } /** * Stage that takes care of the {@link RetryPolicy} handling. */ private Object processRetryStage(FaultToleranceInvocation invocation) throws Exception { - int attemptsLeft = retry.totalAttempts(); + int totalAttempts = retry.totalAttempts(); + int attemptsLeft = totalAttempts; Long retryTimeoutTime = retry.timeoutTimeNow(); + InvocationContext context = invocation.context; while (attemptsLeft > 0) { attemptsLeft--; try { - return isAsynchronous() ? processRetryAsync(invocation) : processCircuitBreakerStage(invocation, null); + boolean firstAttempt = attemptsLeft == totalAttempts - 1; + if (!firstAttempt) { + invocation.metrics.incrementRetryRetriesTotal(context); + } + Object resultValue = isAsynchronous() + ? processRetryAsync(invocation) + : processCircuitBreakerStage(invocation, null); + if (firstAttempt) { + invocation.metrics.incrementRetryCallsSucceededNotRetriedTotal(context); + } else { + invocation.metrics.incrementRetryCallsSucceededRetriedTotal(context); + } + return resultValue; } catch (Exception ex) { if (attemptsLeft <= 0 || !retry.retryOn(ex) || retryTimeoutTime != null && System.currentTimeMillis() >= retryTimeoutTime) { + invocation.metrics.incrementRetryCallsFailedTotal(context); throw ex; } if (retry.isDelayed()) { - invocation.execution.delay(retry.jitteredDelay(), invocation.context); + invocation.execution.delay(retry.jitteredDelay(), context); } } } @@ -263,17 +298,22 @@ private Object processRetryStage(FaultToleranceInvocation invocation) throws Exc } private Object processRetryAsync(FaultToleranceInvocation invocation) throws Exception { - CompletableFuture asyncTry = new CompletableFuture<>(); + CompletableFuture asyncAttempt = new CompletableFuture<>(); //TODO try if it works to use the asyncTry thread as inv asyncResult - invocation.execution.runAsynchronous(asyncTry, - () -> invocation.runStageWithWorker(inv -> processCircuitBreakerStage(inv, asyncTry))); + invocation.execution.runAsynchronous(asyncAttempt, + () -> invocation.runStageWithWorker(() -> processCircuitBreakerStage(invocation, asyncAttempt))); try { - asyncTry.get(); // wait and only proceed on success - //TODO this below check should not be needed since it only could be done from a another try which only riggers if this fails with an exception - // BUT need to remember that cancel can occur + asyncAttempt.get(); // wait and only proceed on success invocation.timeoutIfConcludedConcurrently(); - return asyncTry; - } catch (ExecutionException ex) { + return asyncAttempt; + } catch (ExecutionException ex) { // this ExecutionException is from calling get() above in case completed exceptionally + if (ex.getCause() instanceof ExecutionException) { + // this cause ExecutionException is caused by annotated method returned a Future that completed exceptionally + if (asynchronous.isSuccessWhenCompletedExceptionally()) { + return new CompletableFuture<>().completeExceptionally(ex.getCause()); // only unwrap level added by async processing + } + throw (Exception) ex.getCause().getCause(); // for retry handling return plain cause + } throw (Exception) ex.getCause(); } } @@ -281,46 +321,59 @@ private Object processRetryAsync(FaultToleranceInvocation invocation) throws Exc /** * Stage that takes care of the {@link CircuitBreakerPolicy} handling. */ - private Object processCircuitBreakerStage(FaultToleranceInvocation invocation, CompletableFuture asyncTry) throws Exception { + private Object processCircuitBreakerStage(FaultToleranceInvocation invocation, CompletableFuture asyncAttempt) throws Exception { if (!isCircuitBreakerPresent()) { - return processTimeoutStage(invocation, asyncTry); + return processTimeoutStage(invocation, asyncAttempt); + } + InvocationContext context = invocation.context; + CircuitBreakerState state = invocation.execution.getState(circuitBreaker.requestVolumeThreshold, context); + if (isMetricsEnabled) { + invocation.metrics.insertCircuitbreakerOpenTotal(state::nanosOpen, context); + invocation.metrics.insertCircuitbreakerHalfOpenTotal(state::nanosHalfOpen, context); + invocation.metrics.insertCircuitbreakerClosedTotal(state::nanosClosed, context); } - CircuitBreakerState state = invocation.execution.getState(circuitBreaker.requestVolumeThreshold, invocation.context); Object resultValue = null; switch (state.getCircuitState()) { default: case OPEN: + invocation.metrics.incrementCircuitbreakerCallsPreventedTotal(context); throw new CircuitBreakerOpenException(); case HALF_OPEN: try { - resultValue = processTimeoutStage(invocation, asyncTry); + resultValue = processTimeoutStage(invocation, asyncAttempt); } catch (Exception ex) { + invocation.metrics.incrementCircuitbreakerCallsFailedTotal(context); if (circuitBreaker.failOn(ex)) { + invocation.metrics.incrementCircuitbreakerOpenedTotal(context); state.open(); invocation.execution.scheduleDelayed(circuitBreaker.delay, state::halfOpen); } throw ex; } state.halfOpenSuccessful(circuitBreaker.successThreshold); + invocation.metrics.incrementCircuitbreakerCallsSucceededTotal(context); return resultValue; case CLOSED: Exception failedOn = null; try { - resultValue = processTimeoutStage(invocation, asyncTry); + resultValue = processTimeoutStage(invocation, asyncAttempt); state.recordClosedResult(true); } catch (Exception ex) { + invocation.metrics.incrementCircuitbreakerCallsFailedTotal(context); if (circuitBreaker.failOn(ex)) { state.recordClosedResult(false); } failedOn = ex; } - if (state.isOverFailureThreshold(Math.round(circuitBreaker.requestVolumeThreshold * circuitBreaker.failureRatio))) { + if (state.isOverFailureThreshold(circuitBreaker.requestVolumeThreshold, circuitBreaker.failureRatio)) { + invocation.metrics.incrementCircuitbreakerOpenedTotal(context); state.open(); invocation.execution.scheduleDelayed(circuitBreaker.delay, state::halfOpen); } if (failedOn != null) { throw failedOn; } + invocation.metrics.incrementCircuitbreakerCallsSucceededTotal(context); return resultValue; } } @@ -328,7 +381,7 @@ private Object processCircuitBreakerStage(FaultToleranceInvocation invocation, C /** * Stage that takes care of the {@link TimeoutPolicy} handling. */ - private Object processTimeoutStage(FaultToleranceInvocation invocation, CompletableFuture asyncTry) throws Exception { + private Object processTimeoutStage(FaultToleranceInvocation invocation, CompletableFuture asyncAttempt) throws Exception { if (!isTimeoutPresent()) { return processBulkheadStage(invocation); } @@ -336,14 +389,17 @@ private Object processTimeoutStage(FaultToleranceInvocation invocation, Completa long timeoutTime = System.currentTimeMillis() + timeoutDuration; Thread current = Thread.currentThread(); AtomicBoolean didTimeout = new AtomicBoolean(false); + InvocationContext context = invocation.context; Future timeout = invocation.execution.scheduleDelayed(timeoutDuration, () -> { didTimeout.set(true); current.interrupt(); - if (asyncTry != null) { + invocation.metrics.incrementTimeoutCallsTimedOutTotal(context); + if (asyncAttempt != null) { // we do this since interrupting not necessarily returns directly or ever but the attempt should timeout now - asyncTry.completeExceptionally(new TimeoutException()); + asyncAttempt.completeExceptionally(new TimeoutException()); } }); + long executionStartTime = System.nanoTime(); try { Object resultValue = processBulkheadStage(invocation); if (current.isInterrupted()) { @@ -352,6 +408,7 @@ private Object processTimeoutStage(FaultToleranceInvocation invocation, Completa if (didTimeout.get() || System.currentTimeMillis() > timeoutTime) { throw new TimeoutException(); } + invocation.metrics.incrementTimeoutCallsNotTimedOutTotal(context); return resultValue; } catch (Exception ex) { if ((ex instanceof InterruptedException || ex.getCause() instanceof InterruptedException) @@ -360,6 +417,7 @@ private Object processTimeoutStage(FaultToleranceInvocation invocation, Completa } throw ex; } finally { + invocation.metrics.addTimeoutExecutionDuration(System.nanoTime() - executionStartTime, context); timeout.cancel(true); } } @@ -369,43 +427,61 @@ private Object processTimeoutStage(FaultToleranceInvocation invocation, Completa */ private Object processBulkheadStage(FaultToleranceInvocation invocation) throws Exception { if (!isBulkheadPresent()) { - return processMethodCallStage(invocation); + return processContextProceedStage(invocation); + } + InvocationContext context = invocation.context; + BulkheadSemaphore executionSemaphore = invocation.execution.getExecutionSemaphoreOf(bulkhead.value, context); + if (isMetricsEnabled) { + invocation.metrics.insertBulkheadConcurrentExecutions(executionSemaphore::acquiredPermits, context); } - BulkheadSemaphore executionSemaphore = invocation.execution.getExecutionSemaphoreOf(bulkhead.value, invocation.context); + long executionStartTime = System.nanoTime(); if (executionSemaphore.tryAcquire(0, TimeUnit.SECONDS)) { + invocation.metrics.incrementBulkheadCallsAcceptedTotal(context); + if (isAsynchronous()) { + invocation.metrics.addBulkheadWaitingDuration(0L, context); // we did not wait but need to factor in the invocation for histogram quartiles + } try { - return processMethodCallStage(invocation); + return processContextProceedStage(invocation); } finally { + invocation.metrics.addBulkheadExecutionDuration(System.nanoTime() - executionStartTime, context); executionSemaphore.release(); } } if (!isAsynchronous()) { // plain semaphore style, fail: + invocation.metrics.incrementBulkheadCallsRejectedTotal(context); throw new BulkheadException("No free work permits."); } // from here: queueing style: - BulkheadSemaphore waitingQueueSemaphore = invocation.execution.getWaitingQueueSemaphoreOf(bulkhead.waitingTaskQueue, invocation.context); + BulkheadSemaphore waitingQueueSemaphore = invocation.execution.getWaitingQueueSemaphoreOf(bulkhead.waitingTaskQueue, context); + if (isMetricsEnabled) { + invocation.metrics.insertBulkheadWaitingQueuePopulation(waitingQueueSemaphore::acquiredPermits, context); + } if (waitingQueueSemaphore.tryAcquire(0, TimeUnit.SECONDS)) { + invocation.metrics.incrementBulkheadCallsAcceptedTotal(context); + long queueStartTime = System.nanoTime(); try { - invocation.timeoutIfConcludedConcurrently(); // avoid execution if not needed executionSemaphore.acquire(); // block until execution permit becomes available } catch (InterruptedException ex) { waitingQueueSemaphore.release(); throw new BulkheadException(ex); + } finally { + invocation.metrics.addBulkheadWaitingDuration(System.nanoTime() - queueStartTime, context); } waitingQueueSemaphore.release(); try { - return processMethodCallStage(invocation); + return processContextProceedStage(invocation); } finally { executionSemaphore.release(); } } + invocation.metrics.incrementBulkheadCallsRejectedTotal(context); throw new BulkheadException("No free work or queue permits."); } /** * Stage where the actual wrapped method call occurs. */ - private Object processMethodCallStage(FaultToleranceInvocation invocation) throws Exception { + private Object processContextProceedStage(FaultToleranceInvocation invocation) throws Exception { invocation.timeoutIfConcludedConcurrently(); return invocation.context.proceed(); } diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/RetryPolicy.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/RetryPolicy.java index 7f27f1bbee5..9456e042144 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/RetryPolicy.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/RetryPolicy.java @@ -68,6 +68,10 @@ public static RetryPolicy create(InvocationContext context, FaultToleranceConfig return NONE; } + public boolean isNone() { + return this != NONE; + } + public boolean retryOn(Exception ex) { return isCaught(ex, retryOn) && !isCaught(ex, abortOn); } diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/state/CircuitBreakerState.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/state/CircuitBreakerState.java index 1954bfe5b88..cb854008133 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/state/CircuitBreakerState.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/state/CircuitBreakerState.java @@ -141,10 +141,10 @@ public int getHalfOpenSuccessFulResultCounter() { * @param failureThreshold The number of failures before the circuit breaker should open * @return True if the CircuitBreaker is over the failure threshold */ - public boolean isOverFailureThreshold(long failureThreshold) { + public boolean isOverFailureThreshold(int requestVolumeThreshold, double failureRatio) { boolean over = false; int failures = 0; - + int failureThreshold = (int) Math.round(requestVolumeThreshold * failureRatio); // Only check if the queue is full if (this.closedResultsQueue.remainingCapacity() == 0) { for (Boolean success : this.closedResultsQueue) { @@ -174,15 +174,15 @@ public long updateAndGet(CircuitState circuitState) { : this.allStateTimes.get(circuitState).nanos(); } - public long isOpen() { + public long nanosOpen() { return updateAndGet(CircuitState.OPEN); } - public long isHalfOpen() { + public long nanosHalfOpen() { return updateAndGet(CircuitState.HALF_OPEN); } - public long isClosed() { + public long nanosClosed() { return updateAndGet(CircuitState.CLOSED); } From 09bb6dbddb5ce77a15d4208066b34ad1c6bcd5be Mon Sep 17 00:00:00 2001 From: Jan Bernitt Date: Wed, 17 Apr 2019 21:44:04 +0200 Subject: [PATCH 09/30] PAYARA-3468 metrics TCK tests pass - rename --- ...on.java => FaultToleranceEnvironment.java} | 6 +- .../faulttolerance/FaultToleranceService.java | 8 +- .../BaseFaultToleranceInterceptor.java | 6 +- .../interceptors/BulkheadInterceptor.java | 4 +- .../FaultToleranceInterceptor.java | 10 +- .../interceptors/fallback/FallbackPolicy.java | 6 +- .../policy/FaultTolerancePolicy.java | 111 ++++++++++-------- 7 files changed, 84 insertions(+), 67 deletions(-) rename appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/{FaultToleranceExecution.java => FaultToleranceEnvironment.java} (88%) diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/FaultToleranceExecution.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/FaultToleranceEnvironment.java similarity index 88% rename from appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/FaultToleranceExecution.java rename to appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/FaultToleranceEnvironment.java index 66e59325d07..09ecf6a14a4 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/FaultToleranceExecution.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/FaultToleranceEnvironment.java @@ -13,7 +13,7 @@ import fish.payara.microprofile.faulttolerance.state.BulkheadSemaphore; import fish.payara.microprofile.faulttolerance.state.CircuitBreakerState; -public interface FaultToleranceExecution { +public interface FaultToleranceEnvironment { FaultToleranceConfig getConfig(InvocationContext context, Stereotypes stereotypes); @@ -21,9 +21,9 @@ public interface FaultToleranceExecution { CircuitBreakerState getState(int requestVolumeThreshold, InvocationContext context); - BulkheadSemaphore getExecutionSemaphoreOf(int maxConcurrentThreads, InvocationContext context); + BulkheadSemaphore getConcurrentExecutions(int maxConcurrentThreads, InvocationContext context); - BulkheadSemaphore getWaitingQueueSemaphoreOf(int queueCapacity, InvocationContext context); + BulkheadSemaphore getWaitingQueuePopulation(int queueCapacity, InvocationContext context); void delay(long delayMillis, InvocationContext context) throws InterruptedException; diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/FaultToleranceService.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/FaultToleranceService.java index 0b7075dd32e..7581e00f35e 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/FaultToleranceService.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/FaultToleranceService.java @@ -95,10 +95,10 @@ * * @author Andrew Pielage */ -@ContractsProvided(FaultToleranceExecution.class) +@ContractsProvided(FaultToleranceEnvironment.class) @Service(name = "microprofile-fault-tolerance-service") @RunLevel(StartupRunLevel.VAL) -public class FaultToleranceService implements EventListener, FaultToleranceExecution { +public class FaultToleranceService implements EventListener, FaultToleranceEnvironment { private static final Logger logger = Logger.getLogger(FaultToleranceService.class.getName()); @@ -290,13 +290,13 @@ public CircuitBreakerState getState(int requestVolumeThreshold, InvocationContex } @Override - public BulkheadSemaphore getExecutionSemaphoreOf(int maxConcurrentThreads, InvocationContext context) { + public BulkheadSemaphore getConcurrentExecutions(int maxConcurrentThreads, InvocationContext context) { return getBulkheadExecutionSemaphore(getApplicationContext(context), context.getTarget(), context.getMethod(), maxConcurrentThreads); } @Override - public BulkheadSemaphore getWaitingQueueSemaphoreOf(int queueCapacity, InvocationContext context) { + public BulkheadSemaphore getWaitingQueuePopulation(int queueCapacity, InvocationContext context) { return getBulkheadExecutionQueueSemaphore(getApplicationContext(context), context.getTarget(), context.getMethod(), queueCapacity); } diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/BaseFaultToleranceInterceptor.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/BaseFaultToleranceInterceptor.java index 2d7f9dd2269..0020f1c2080 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/BaseFaultToleranceInterceptor.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/BaseFaultToleranceInterceptor.java @@ -14,7 +14,7 @@ import fish.payara.microprofile.faulttolerance.FaultToleranceConfig; import fish.payara.microprofile.faulttolerance.FaultToleranceMetrics; -import fish.payara.microprofile.faulttolerance.FaultToleranceExecution; +import fish.payara.microprofile.faulttolerance.FaultToleranceEnvironment; import fish.payara.microprofile.faulttolerance.interceptors.fallback.FallbackPolicy; public abstract class BaseFaultToleranceInterceptor { @@ -24,7 +24,7 @@ public abstract class BaseFaultToleranceInterceptor { private final Class annotationType; private final boolean throwExceptionForRetry; private FaultToleranceConfig config = FaultToleranceConfig.ANNOTATED; - private FaultToleranceExecution execution; + private FaultToleranceEnvironment execution; private FaultToleranceMetrics metrics; protected BaseFaultToleranceInterceptor(Class annotationType, boolean throwExceptionForRetry) { @@ -46,7 +46,7 @@ public FaultToleranceMetrics getMetrics() { return metrics; } - public FaultToleranceExecution getExecution() { + public FaultToleranceEnvironment getExecution() { return execution; } diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/BulkheadInterceptor.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/BulkheadInterceptor.java index f1fd7183576..d096f866bdc 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/BulkheadInterceptor.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/BulkheadInterceptor.java @@ -133,7 +133,7 @@ private Object bulkhead(InvocationContext context) throws Exception { Bulkhead bulkhead = getConfig().getAnnotation(Bulkhead.class, context); - BulkheadSemaphore bulkheadExecutionSemaphore = getExecution().getExecutionSemaphoreOf(getConfig().value(bulkhead, context), context); + BulkheadSemaphore bulkheadExecutionSemaphore = getExecution().getConcurrentExecutions(getConfig().value(bulkhead, context), context); if (getConfig().isMetricsEnabled(context)) { getMetrics().insertBulkheadConcurrentExecutions(bulkheadExecutionSemaphore::acquiredPermits, context); @@ -141,7 +141,7 @@ private Object bulkhead(InvocationContext context) throws Exception { // If the Asynchronous annotation is present, use threadpool style, otherwise use semaphore style if (getConfig().getAnnotation(Asynchronous.class, context) != null) { - BulkheadSemaphore bulkheadExecutionQueueSemaphore = getExecution().getWaitingQueueSemaphoreOf(getConfig().waitingTaskQueue(bulkhead, context), context); + BulkheadSemaphore bulkheadExecutionQueueSemaphore = getExecution().getWaitingQueuePopulation(getConfig().waitingTaskQueue(bulkhead, context), context); if (getConfig().isMetricsEnabled(context)) { getMetrics().insertBulkheadWaitingQueuePopulation(bulkheadExecutionQueueSemaphore::acquiredPermits, context); diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/FaultToleranceInterceptor.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/FaultToleranceInterceptor.java index 1f7fa69b6be..7e94ffd72bd 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/FaultToleranceInterceptor.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/FaultToleranceInterceptor.java @@ -17,7 +17,7 @@ import org.eclipse.microprofile.faulttolerance.exceptions.FaultToleranceDefinitionException; import org.glassfish.internal.api.Globals; -import fish.payara.microprofile.faulttolerance.FaultToleranceExecution; +import fish.payara.microprofile.faulttolerance.FaultToleranceEnvironment; import fish.payara.microprofile.faulttolerance.cdi.FaultToleranceCdiUtils.Stereotypes; import fish.payara.microprofile.faulttolerance.policy.FaultTolerancePolicy; @@ -34,11 +34,11 @@ public class FaultToleranceInterceptor implements Stereotypes, Serializable, Pri @AroundInvoke public Object intercept(InvocationContext context) throws Exception { try { - FaultToleranceExecution execution = - Globals.getDefaultBaseServiceLocator().getService(FaultToleranceExecution.class); - FaultTolerancePolicy policy = FaultTolerancePolicy.get(context, () -> execution.getConfig(context, this)); + FaultToleranceEnvironment env = + Globals.getDefaultBaseServiceLocator().getService(FaultToleranceEnvironment.class); + FaultTolerancePolicy policy = FaultTolerancePolicy.get(context, () -> env.getConfig(context, this)); if (policy.isPresent) { - return policy.proceed(context, execution); + return policy.proceed(context, env); } } catch (FaultToleranceDefinitionException e) { logger.log(Level.SEVERE, "Effective FT policy contains illegal values, fault tolerance cannot be applied," diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/fallback/FallbackPolicy.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/fallback/FallbackPolicy.java index 32c049fd6ab..658ab1bd37d 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/fallback/FallbackPolicy.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/fallback/FallbackPolicy.java @@ -40,7 +40,7 @@ package fish.payara.microprofile.faulttolerance.interceptors.fallback; import fish.payara.microprofile.faulttolerance.FaultToleranceConfig; -import fish.payara.microprofile.faulttolerance.FaultToleranceExecution; +import fish.payara.microprofile.faulttolerance.FaultToleranceEnvironment; import fish.payara.microprofile.faulttolerance.FaultToleranceExecutionContext; import fish.payara.microprofile.faulttolerance.FaultToleranceMetrics; import fish.payara.microprofile.faulttolerance.cdi.FaultToleranceCdiUtils; @@ -64,10 +64,10 @@ public class FallbackPolicy { private final Class> fallbackClass; private final String fallbackMethod; - private final FaultToleranceExecution execution; + private final FaultToleranceEnvironment execution; private final FaultToleranceMetrics metrics; - public FallbackPolicy(Fallback fallback, FaultToleranceConfig config, FaultToleranceExecution execution, FaultToleranceMetrics metrics, + public FallbackPolicy(Fallback fallback, FaultToleranceConfig config, FaultToleranceEnvironment execution, FaultToleranceMetrics metrics, InvocationContext context) { this.execution = execution; this.metrics = metrics; diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/FaultTolerancePolicy.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/FaultTolerancePolicy.java index 570245d1b80..aa41d3b4e83 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/FaultTolerancePolicy.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/FaultTolerancePolicy.java @@ -6,14 +6,12 @@ import java.util.Set; import java.util.concurrent.Callable; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionStage; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Supplier; -import java.util.logging.Logger; import javax.interceptor.InvocationContext; @@ -24,7 +22,7 @@ import org.eclipse.microprofile.faulttolerance.exceptions.TimeoutException; import fish.payara.microprofile.faulttolerance.FaultToleranceConfig; -import fish.payara.microprofile.faulttolerance.FaultToleranceExecution; +import fish.payara.microprofile.faulttolerance.FaultToleranceEnvironment; import fish.payara.microprofile.faulttolerance.FaultToleranceMetrics; import fish.payara.microprofile.faulttolerance.state.BulkheadSemaphore; import fish.payara.microprofile.faulttolerance.state.CircuitBreakerState; @@ -45,8 +43,6 @@ */ public final class FaultTolerancePolicy implements Serializable { - private static final Logger logger = Logger.getLogger(FaultTolerancePolicy.class.getName()); - private static final long TTL = 60 * 1000; private static final ConcurrentHashMap, ConcurrentHashMap> POLICY_BY_METHOD = new ConcurrentHashMap<>(); @@ -147,15 +143,15 @@ public boolean isTimeoutPresent() { static final class FaultToleranceInvocation { final InvocationContext context; - final FaultToleranceExecution execution; + final FaultToleranceEnvironment env; final FaultToleranceMetrics metrics; final CompletableFuture asyncResult; final Set asyncWorkers; - FaultToleranceInvocation(InvocationContext context, FaultToleranceExecution execution, FaultToleranceMetrics metrics, + FaultToleranceInvocation(InvocationContext context, FaultToleranceEnvironment env, FaultToleranceMetrics metrics, CompletableFuture asyncResult, Set asyncWorkers) { this.context = context; - this.execution = execution; + this.env = env; this.metrics = metrics; this.asyncResult = asyncResult; this.asyncWorkers = asyncWorkers; @@ -179,16 +175,38 @@ void timeoutIfConcludedConcurrently() throws TimeoutException { } } - public Object proceed(InvocationContext context, FaultToleranceExecution execution) throws Exception { + /** + * Wraps {@link InvocationContext#proceed()} with fault tolerance behaviour. + * + * Processing has 6 stages: + *
+     * 1) Asynchronous 
+     * 2) Fallback 
+     * 3) Retry 
+     * 4) Circuit Breaker 
+     * 5) Timeout 
+     * 6) Bulkhead
+     * 
+ * The call chain goes from 1) down to 6) skipping stages that are not requested by this policy. + * + * Asynchronous execution branches to new threads in stage 1) and 3) each executed by the + * {@link FaultToleranceEnvironment#runAsynchronous(CompletableFuture, Callable)}. + * + * @param context intercepted call context + * @param env the environment used to execute the FT behaviour + * @return the result of {@link InvocationContext#proceed()} wrapped with FT behaviour + * @throws Exception as thrown by the wrapped invocation or a {@link FaultToleranceException} + */ + public Object proceed(InvocationContext context, FaultToleranceEnvironment env) throws Exception { if (!isPresent) { return context.proceed(); } FaultToleranceMetrics metrics = isMetricsEnabled - ? execution.getMetrics(context) + ? env.getMetrics(context) : FaultToleranceMetrics.DISABLED; try { metrics.incrementInvocationsTotal(context); - return processAsynchronousStage(context, execution, metrics); + return processAsynchronousStage(context, env, metrics); } catch (Exception e) { metrics.incrementInvocationsFailedTotal(context); throw e; @@ -198,19 +216,19 @@ public Object proceed(InvocationContext context, FaultToleranceExecution executi /** * Stage that takes care of the {@link AsynchronousPolicy} handling. */ - private Object processAsynchronousStage(InvocationContext context, FaultToleranceExecution execution, + private Object processAsynchronousStage(InvocationContext context, FaultToleranceEnvironment env, FaultToleranceMetrics metrics) throws Exception { if (!isAsynchronous()) { - return processFallbackStage(new FaultToleranceInvocation(context, execution, metrics, null, null)); + return processFallbackStage(new FaultToleranceInvocation(context, env, metrics, null, null)); } - Set asyncWorkers = ConcurrentHashMap.newKeySet(); + Set workers = ConcurrentHashMap.newKeySet(); CompletableFuture asyncResult = new CompletableFuture() { @Override public boolean cancel(boolean mayInterruptIfRunning) { if (super.cancel(mayInterruptIfRunning)) { if (mayInterruptIfRunning) { - asyncWorkers.forEach(worker -> worker.interrupt()); + workers.forEach(worker -> worker.interrupt()); } return true; } @@ -226,15 +244,14 @@ public boolean completeExceptionally(Throwable ex) { if (ex instanceof ExecutionException) { metrics.incrementInvocationsFailedTotal(context); return super.completeExceptionally(ex.getCause()); - } else if (!asynchronous.isSuccessWhenCompletedExceptionally()) { + } else if (ex instanceof FaultToleranceException || !asynchronous.isSuccessWhenCompletedExceptionally()) { metrics.incrementInvocationsFailedTotal(context); } return super.completeExceptionally(ex); } }; - FaultToleranceInvocation invocation = new FaultToleranceInvocation(context, execution, metrics, asyncResult, - asyncWorkers); - execution.runAsynchronous(asyncResult, + FaultToleranceInvocation invocation = new FaultToleranceInvocation(context, env, metrics, asyncResult, workers); + env.runAsynchronous(asyncResult, () -> invocation.runStageWithWorker(() -> processFallbackStage(invocation))); return asyncResult; } @@ -251,9 +268,9 @@ private Object processFallbackStage(FaultToleranceInvocation invocation) throws } catch (Exception ex) { invocation.metrics.incrementFallbackCallsTotal(invocation.context); if (fallback.isHandlerPresent()) { - return invocation.execution.fallbackHandle(fallback.value, invocation.context, ex); + return invocation.env.fallbackHandle(fallback.value, invocation.context, ex); } - return invocation.execution.fallbackInvoke(fallback.method, invocation.context); + return invocation.env.fallbackInvoke(fallback.method, invocation.context); } } @@ -289,7 +306,7 @@ private Object processRetryStage(FaultToleranceInvocation invocation) throws Exc throw ex; } if (retry.isDelayed()) { - invocation.execution.delay(retry.jitteredDelay(), context); + invocation.env.delay(retry.jitteredDelay(), context); } } } @@ -299,8 +316,7 @@ private Object processRetryStage(FaultToleranceInvocation invocation) throws Exc private Object processRetryAsync(FaultToleranceInvocation invocation) throws Exception { CompletableFuture asyncAttempt = new CompletableFuture<>(); - //TODO try if it works to use the asyncTry thread as inv asyncResult - invocation.execution.runAsynchronous(asyncAttempt, + invocation.env.runAsynchronous(asyncAttempt, () -> invocation.runStageWithWorker(() -> processCircuitBreakerStage(invocation, asyncAttempt))); try { asyncAttempt.get(); // wait and only proceed on success @@ -326,7 +342,7 @@ private Object processCircuitBreakerStage(FaultToleranceInvocation invocation, C return processTimeoutStage(invocation, asyncAttempt); } InvocationContext context = invocation.context; - CircuitBreakerState state = invocation.execution.getState(circuitBreaker.requestVolumeThreshold, context); + CircuitBreakerState state = invocation.env.getState(circuitBreaker.requestVolumeThreshold, context); if (isMetricsEnabled) { invocation.metrics.insertCircuitbreakerOpenTotal(state::nanosOpen, context); invocation.metrics.insertCircuitbreakerHalfOpenTotal(state::nanosHalfOpen, context); @@ -346,7 +362,7 @@ private Object processCircuitBreakerStage(FaultToleranceInvocation invocation, C if (circuitBreaker.failOn(ex)) { invocation.metrics.incrementCircuitbreakerOpenedTotal(context); state.open(); - invocation.execution.scheduleDelayed(circuitBreaker.delay, state::halfOpen); + invocation.env.scheduleDelayed(circuitBreaker.delay, state::halfOpen); } throw ex; } @@ -368,7 +384,7 @@ private Object processCircuitBreakerStage(FaultToleranceInvocation invocation, C if (state.isOverFailureThreshold(circuitBreaker.requestVolumeThreshold, circuitBreaker.failureRatio)) { invocation.metrics.incrementCircuitbreakerOpenedTotal(context); state.open(); - invocation.execution.scheduleDelayed(circuitBreaker.delay, state::halfOpen); + invocation.env.scheduleDelayed(circuitBreaker.delay, state::halfOpen); } if (failedOn != null) { throw failedOn; @@ -390,7 +406,7 @@ private Object processTimeoutStage(FaultToleranceInvocation invocation, Completa Thread current = Thread.currentThread(); AtomicBoolean didTimeout = new AtomicBoolean(false); InvocationContext context = invocation.context; - Future timeout = invocation.execution.scheduleDelayed(timeoutDuration, () -> { + Future timeout = invocation.env.scheduleDelayed(timeoutDuration, () -> { didTimeout.set(true); current.interrupt(); invocation.metrics.incrementTimeoutCallsTimedOutTotal(context); @@ -427,51 +443,52 @@ private Object processTimeoutStage(FaultToleranceInvocation invocation, Completa */ private Object processBulkheadStage(FaultToleranceInvocation invocation) throws Exception { if (!isBulkheadPresent()) { - return processContextProceedStage(invocation); + return proceed(invocation); } InvocationContext context = invocation.context; - BulkheadSemaphore executionSemaphore = invocation.execution.getExecutionSemaphoreOf(bulkhead.value, context); + BulkheadSemaphore concurrentExecutions = invocation.env.getConcurrentExecutions(bulkhead.value, context); + BulkheadSemaphore waitingQueuePopulation = !isAsynchronous() ? null + : invocation.env.getWaitingQueuePopulation(bulkhead.waitingTaskQueue, context); if (isMetricsEnabled) { - invocation.metrics.insertBulkheadConcurrentExecutions(executionSemaphore::acquiredPermits, context); + invocation.metrics.insertBulkheadConcurrentExecutions(concurrentExecutions::acquiredPermits, context); + if (waitingQueuePopulation != null) { + invocation.metrics.insertBulkheadWaitingQueuePopulation(waitingQueuePopulation::acquiredPermits, context); + } } long executionStartTime = System.nanoTime(); - if (executionSemaphore.tryAcquire(0, TimeUnit.SECONDS)) { + if (concurrentExecutions.tryAcquire(0, TimeUnit.SECONDS)) { invocation.metrics.incrementBulkheadCallsAcceptedTotal(context); if (isAsynchronous()) { invocation.metrics.addBulkheadWaitingDuration(0L, context); // we did not wait but need to factor in the invocation for histogram quartiles } try { - return processContextProceedStage(invocation); + return proceed(invocation); } finally { invocation.metrics.addBulkheadExecutionDuration(System.nanoTime() - executionStartTime, context); - executionSemaphore.release(); + concurrentExecutions.release(); } } - if (!isAsynchronous()) { // plain semaphore style, fail: + if (waitingQueuePopulation == null) { // plain semaphore style, fail: invocation.metrics.incrementBulkheadCallsRejectedTotal(context); throw new BulkheadException("No free work permits."); } // from here: queueing style: - BulkheadSemaphore waitingQueueSemaphore = invocation.execution.getWaitingQueueSemaphoreOf(bulkhead.waitingTaskQueue, context); - if (isMetricsEnabled) { - invocation.metrics.insertBulkheadWaitingQueuePopulation(waitingQueueSemaphore::acquiredPermits, context); - } - if (waitingQueueSemaphore.tryAcquire(0, TimeUnit.SECONDS)) { + if (waitingQueuePopulation.tryAcquire(0, TimeUnit.SECONDS)) { invocation.metrics.incrementBulkheadCallsAcceptedTotal(context); long queueStartTime = System.nanoTime(); try { - executionSemaphore.acquire(); // block until execution permit becomes available + concurrentExecutions.acquire(); // block until execution permit becomes available } catch (InterruptedException ex) { - waitingQueueSemaphore.release(); + waitingQueuePopulation.release(); throw new BulkheadException(ex); } finally { invocation.metrics.addBulkheadWaitingDuration(System.nanoTime() - queueStartTime, context); } - waitingQueueSemaphore.release(); + waitingQueuePopulation.release(); try { - return processContextProceedStage(invocation); + return proceed(invocation); } finally { - executionSemaphore.release(); + concurrentExecutions.release(); } } invocation.metrics.incrementBulkheadCallsRejectedTotal(context); @@ -479,9 +496,9 @@ private Object processBulkheadStage(FaultToleranceInvocation invocation) throws } /** - * Stage where the actual wrapped method call occurs. + * Final stage where the actual wrapped method call occurs. */ - private Object processContextProceedStage(FaultToleranceInvocation invocation) throws Exception { + private static Object proceed(FaultToleranceInvocation invocation) throws Exception { invocation.timeoutIfConcludedConcurrently(); return invocation.context.proceed(); } From b7a71053ef5d32cb4103eaed3e56268e06f27771 Mon Sep 17 00:00:00 2001 From: Jan Bernitt Date: Thu, 18 Apr 2019 13:16:30 +0200 Subject: [PATCH 10/30] PAYARA-3468 config and metrics as service factories, extracted service package --- .../faulttolerance/FaultToleranceConfig.java | 85 ++-- .../FaultToleranceEnvironment.java | 45 --- .../faulttolerance/FaultToleranceMetrics.java | 100 +++-- .../faulttolerance/FaultToleranceService.java | 365 +---------------- .../cdi/CdiFaultToleranceConfig.java | 297 -------------- .../cdi/CdiFaultToleranceMetrics.java | 56 --- .../cdi/FaultToleranceCDIExtension.java | 20 +- .../interceptors/AsynchronousInterceptor.java | 6 +- .../BaseFaultToleranceInterceptor.java | 45 +-- .../interceptors/BulkheadInterceptor.java | 60 +-- .../CircuitBreakerInterceptor.java | 50 +-- .../FaultToleranceInterceptor.java | 8 +- .../interceptors/RetryInterceptor.java | 50 +-- .../interceptors/TimeoutInterceptor.java | 38 +- .../interceptors/fallback/FallbackPolicy.java | 18 +- .../policy/AsynchronousPolicy.java | 4 +- .../faulttolerance/policy/BulkheadPolicy.java | 10 +- .../policy/CircuitBreakerPolicy.java | 18 +- .../faulttolerance/policy/FallbackPolicy.java | 10 +- .../policy/FaultTolerancePolicy.java | 120 +++--- .../faulttolerance/policy/Policy.java | 5 + .../faulttolerance/policy/RetryPolicy.java | 24 +- ...ontext.java => StaticAnalysisContext.java} | 19 +- .../faulttolerance/policy/TimeoutPolicy.java | 10 +- .../FaultToleranceApplicationState.java | 11 +- .../service/FaultToleranceConfigFactory.java | 317 +++++++++++++++ .../service/FaultToleranceMetricsFactory.java | 94 +++++ .../service/FaultToleranceServiceImpl.java | 371 ++++++++++++++++++ .../FaultToleranceUtils.java} | 33 +- .../faulttolerance/service/Stereotypes.java | 16 + .../validators/BulkheadValidator.java | 7 +- .../validators/CircuitBreakerValidator.java | 11 +- .../validators/FallbackValidator.java | 7 +- .../validators/RetryValidator.java | 11 +- .../validators/TimeoutValidator.java | 5 +- .../faulttolerance/test/TestUtils.java | 3 +- 36 files changed, 1213 insertions(+), 1136 deletions(-) delete mode 100644 appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/FaultToleranceEnvironment.java delete mode 100644 appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/cdi/CdiFaultToleranceConfig.java delete mode 100644 appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/cdi/CdiFaultToleranceMetrics.java rename appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/{StaticAnalysisMethodContext.java => StaticAnalysisContext.java} (63%) rename appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/{ => service}/FaultToleranceApplicationState.java (89%) create mode 100644 appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/service/FaultToleranceConfigFactory.java create mode 100644 appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/service/FaultToleranceMetricsFactory.java create mode 100644 appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/service/FaultToleranceServiceImpl.java rename appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/{cdi/FaultToleranceCdiUtils.java => service/FaultToleranceUtils.java} (92%) create mode 100644 appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/service/Stereotypes.java diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/FaultToleranceConfig.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/FaultToleranceConfig.java index 801863344f9..ec7b6bfdfaf 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/FaultToleranceConfig.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/FaultToleranceConfig.java @@ -1,11 +1,10 @@ package fish.payara.microprofile.faulttolerance; import java.lang.annotation.Annotation; +import java.lang.reflect.Method; import java.time.temporal.ChronoUnit; -import javax.enterprise.inject.spi.BeanManager; import javax.interceptor.Interceptor; -import javax.interceptor.InvocationContext; import org.eclipse.microprofile.faulttolerance.Bulkhead; import org.eclipse.microprofile.faulttolerance.CircuitBreaker; @@ -13,9 +12,6 @@ import org.eclipse.microprofile.faulttolerance.FallbackHandler; import org.eclipse.microprofile.faulttolerance.Retry; import org.eclipse.microprofile.faulttolerance.Timeout; -import org.jvnet.hk2.annotations.Contract; - -import fish.payara.microprofile.faulttolerance.state.CircuitBreakerState; /** * Encapsulates all properties extracted from FT annotations and the {@link org.eclipse.microprofile.config.Config} so @@ -23,44 +19,49 @@ * * The default implementations provided will extract properties plain from the given annotations. */ -@SuppressWarnings("unused") -@Contract +@FunctionalInterface public interface FaultToleranceConfig { + int DEFAULT_INTERCEPTOR_PRIORITY = Interceptor.Priority.PLATFORM_AFTER + 15; + /** * FT behaves as stated by the present FT annotations. */ - FaultToleranceConfig ANNOTATED = new FaultToleranceConfig() { - // uses default methods - }; + static FaultToleranceConfig asAnnotated(Class target, Method method) { + return new FaultToleranceConfig() { + @Override + public A getAnnotation(Class annotationType) { + A annotation = method.getAnnotation(annotationType); + return annotation != null ? annotation : target.getAnnotation(annotationType); + } + }; + } + + A getAnnotation(Class annotationType); /* * General */ - default boolean isNonFallbackEnabled(InvocationContext context) { + default boolean isNonFallbackEnabled() { return true; } - default boolean isEnabled(Class annotationType, InvocationContext context) { + @SuppressWarnings("unused") + default boolean isEnabled(Class annotationType) { return true; } - default boolean isMetricsEnabled(InvocationContext context) { + default boolean isMetricsEnabled() { return true; } - default A getAnnotation(Class annotationType, InvocationContext context) { - A annotation = context.getMethod().getAnnotation(annotationType); - return annotation != null ? annotation : context.getMethod().getDeclaringClass().getAnnotation(annotationType); - } - - default boolean isAnnotationPresent(Class annotationType, InvocationContext context) { - return getAnnotation(annotationType, context) != null; + default boolean isAnnotationPresent(Class annotationType) { + return getAnnotation(annotationType) != null; } default int interceptorPriority() { - return Interceptor.Priority.PLATFORM_AFTER + 15; + return DEFAULT_INTERCEPTOR_PRIORITY; } @@ -68,39 +69,39 @@ default int interceptorPriority() { * Retry */ - default int maxRetries(Retry annotation, InvocationContext context) { + default int maxRetries(Retry annotation) { return annotation.maxRetries(); } - default long delay(Retry annotation, InvocationContext context) { + default long delay(Retry annotation) { return annotation.delay(); } - default ChronoUnit delayUnit(Retry annotation, InvocationContext context) { + default ChronoUnit delayUnit(Retry annotation) { return annotation.delayUnit(); } - default long maxDuration(Retry annotation, InvocationContext context) { + default long maxDuration(Retry annotation) { return annotation.maxDuration(); } - default ChronoUnit durationUnit(Retry annotation, InvocationContext context) { + default ChronoUnit durationUnit(Retry annotation) { return annotation.durationUnit(); } - default long jitter(Retry annotation, InvocationContext context) { + default long jitter(Retry annotation) { return annotation.jitter(); } - default ChronoUnit jitterDelayUnit(Retry annotation, InvocationContext context) { + default ChronoUnit jitterDelayUnit(Retry annotation) { return annotation.jitterDelayUnit(); } - default Class[] retryOn(Retry annotation, InvocationContext context) { + default Class[] retryOn(Retry annotation) { return annotation.retryOn(); } - default Class[] abortOn(Retry annotation, InvocationContext context) { + default Class[] abortOn(Retry annotation) { return annotation.abortOn(); } @@ -109,27 +110,27 @@ default Class[] abortOn(Retry annotation, InvocationContext * Circuit-Breaker */ - default Class[] failOn(CircuitBreaker annotation, InvocationContext context) { + default Class[] failOn(CircuitBreaker annotation) { return annotation.failOn(); } - default long delay(CircuitBreaker annotation, InvocationContext context) { + default long delay(CircuitBreaker annotation) { return annotation.delay(); } - default ChronoUnit delayUnit(CircuitBreaker annotation, InvocationContext context) { + default ChronoUnit delayUnit(CircuitBreaker annotation) { return annotation.delayUnit(); } - default int requestVolumeThreshold(CircuitBreaker annotation, InvocationContext context) { + default int requestVolumeThreshold(CircuitBreaker annotation) { return annotation.requestVolumeThreshold(); } - default double failureRatio(CircuitBreaker annotation, InvocationContext context) { + default double failureRatio(CircuitBreaker annotation) { return annotation.failureRatio(); } - default int successThreshold(CircuitBreaker annotation, InvocationContext context) { + default int successThreshold(CircuitBreaker annotation) { return annotation.successThreshold(); } @@ -138,11 +139,11 @@ default int successThreshold(CircuitBreaker annotation, InvocationContext contex * Bulkhead */ - default int value(Bulkhead annotation, InvocationContext context) { + default int value(Bulkhead annotation) { return annotation.value(); } - default int waitingTaskQueue(Bulkhead annotation, InvocationContext context) { + default int waitingTaskQueue(Bulkhead annotation) { return annotation.waitingTaskQueue(); } @@ -151,11 +152,11 @@ default int waitingTaskQueue(Bulkhead annotation, InvocationContext context) { * Timeout */ - default long value(Timeout annotation, InvocationContext context) { + default long value(Timeout annotation) { return annotation.value(); } - default ChronoUnit unit(Timeout annotation, InvocationContext context) { + default ChronoUnit unit(Timeout annotation) { return annotation.unit(); } @@ -164,11 +165,11 @@ default ChronoUnit unit(Timeout annotation, InvocationContext context) { * Fallback */ - default Class> value(Fallback annotation, InvocationContext context) { + default Class> value(Fallback annotation) { return annotation.value(); } - default String fallbackMethod(Fallback annotation, InvocationContext context) { + default String fallbackMethod(Fallback annotation) { return annotation.fallbackMethod(); } } diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/FaultToleranceEnvironment.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/FaultToleranceEnvironment.java deleted file mode 100644 index 09ecf6a14a4..00000000000 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/FaultToleranceEnvironment.java +++ /dev/null @@ -1,45 +0,0 @@ -package fish.payara.microprofile.faulttolerance; - -import java.lang.reflect.Method; -import java.util.concurrent.Callable; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.Future; - -import javax.interceptor.InvocationContext; - -import org.eclipse.microprofile.faulttolerance.FallbackHandler; - -import fish.payara.microprofile.faulttolerance.cdi.FaultToleranceCdiUtils.Stereotypes; -import fish.payara.microprofile.faulttolerance.state.BulkheadSemaphore; -import fish.payara.microprofile.faulttolerance.state.CircuitBreakerState; - -public interface FaultToleranceEnvironment { - - FaultToleranceConfig getConfig(InvocationContext context, Stereotypes stereotypes); - - FaultToleranceMetrics getMetrics(InvocationContext context); - - CircuitBreakerState getState(int requestVolumeThreshold, InvocationContext context); - - BulkheadSemaphore getConcurrentExecutions(int maxConcurrentThreads, InvocationContext context); - - BulkheadSemaphore getWaitingQueuePopulation(int queueCapacity, InvocationContext context); - - void delay(long delayMillis, InvocationContext context) throws InterruptedException; - - void runAsynchronous(CompletableFuture asyncResult, Callable operation) throws Exception; - - /** - * @return A future that can be cancelled if the method execution completes before the interrupt happens - */ - Future scheduleDelayed(long delayMillis, Runnable operation) throws Exception; - - Object fallbackHandle(Class> fallbackClass, InvocationContext context, Exception exception) throws Exception; - - Object fallbackInvoke(Method fallbackMethod, InvocationContext context) throws Exception; - - void startTrace(String method, InvocationContext context); - - void endTrace(); - -} diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/FaultToleranceMetrics.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/FaultToleranceMetrics.java index 5f1c1097cc9..3f0aabea2e2 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/FaultToleranceMetrics.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/FaultToleranceMetrics.java @@ -2,8 +2,6 @@ import java.util.function.LongSupplier; -import javax.interceptor.InvocationContext; - /** * Encodes the specifics of the FT metrics names using default methods while decoupling * {@link org.eclipse.microprofile.metrics.MetricRegistry}. @@ -25,7 +23,7 @@ public interface FaultToleranceMetrics { * @param annotationType * @param context */ - default void increment(String metric, InvocationContext context) { + default void increment(String metric) { //NOOP (used when metrics are disabled) } @@ -37,7 +35,7 @@ default void increment(String metric, InvocationContext context) { * @param annotationType * @param context */ - default void add(String metric, long nanos, InvocationContext context) { + default void add(String metric, long nanos) { //NOOP (used when metrics are disabled) } @@ -49,7 +47,7 @@ default void add(String metric, long nanos, InvocationContext context) { * @param annotationType * @param context */ - default void insert(String metric, LongSupplier gauge, InvocationContext context) { + default void insert(String metric, LongSupplier gauge) { //NOOP (used when metrics are disabled) } @@ -58,12 +56,12 @@ default void insert(String metric, LongSupplier gauge, InvocationContext context * @Retry, @Timeout, @CircuitBreaker, @Bulkhead and @Fallback */ - default void incrementInvocationsTotal(InvocationContext context) { - increment("ft.%s.invocations.total", context); + default void incrementInvocationsTotal() { + increment("ft.%s.invocations.total"); } - default void incrementInvocationsFailedTotal(InvocationContext context) { - increment("ft.%s.invocations.failed.total", context); + default void incrementInvocationsFailedTotal() { + increment("ft.%s.invocations.failed.total"); } @@ -71,20 +69,20 @@ default void incrementInvocationsFailedTotal(InvocationContext context) { * @Retry */ - default void incrementRetryCallsSucceededNotRetriedTotal(InvocationContext context) { - increment("ft.%s.retry.callsSucceededNotRetried.total", context); + default void incrementRetryCallsSucceededNotRetriedTotal() { + increment("ft.%s.retry.callsSucceededNotRetried.total"); } - default void incrementRetryCallsSucceededRetriedTotal(InvocationContext context) { - increment("ft.%s.retry.callsSucceededRetried.total", context); + default void incrementRetryCallsSucceededRetriedTotal() { + increment("ft.%s.retry.callsSucceededRetried.total"); } - default void incrementRetryCallsFailedTotal(InvocationContext context) { - increment("ft.%s.retry.callsFailed.total", context); + default void incrementRetryCallsFailedTotal() { + increment("ft.%s.retry.callsFailed.total"); } - default void incrementRetryRetriesTotal(InvocationContext context) { - increment("ft.%s.retry.retries.total", context); + default void incrementRetryRetriesTotal() { + increment("ft.%s.retry.retries.total"); } @@ -92,16 +90,16 @@ default void incrementRetryRetriesTotal(InvocationContext context) { * @Timeout */ - default void addTimeoutExecutionDuration(long duration, InvocationContext context) { - add("ft.%s.timeout.executionDuration", duration, context); + default void addTimeoutExecutionDuration(long duration) { + add("ft.%s.timeout.executionDuration", duration); } - default void incrementTimeoutCallsTimedOutTotal(InvocationContext context) { - increment("ft.%s.timeout.callsTimedOut.total", context); + default void incrementTimeoutCallsTimedOutTotal() { + increment("ft.%s.timeout.callsTimedOut.total"); } - default void incrementTimeoutCallsNotTimedOutTotal(InvocationContext context) { - increment("ft.%s.timeout.callsNotTimedOut.total", context); + default void incrementTimeoutCallsNotTimedOutTotal() { + increment("ft.%s.timeout.callsNotTimedOut.total"); } @@ -109,32 +107,32 @@ default void incrementTimeoutCallsNotTimedOutTotal(InvocationContext context) { * @CircuitBreaker */ - default void incrementCircuitbreakerCallsSucceededTotal(InvocationContext context) { - increment("ft.%s.circuitbreaker.callsSucceeded.total", context); + default void incrementCircuitbreakerCallsSucceededTotal() { + increment("ft.%s.circuitbreaker.callsSucceeded.total"); } - default void incrementCircuitbreakerCallsFailedTotal(InvocationContext context) { - increment("ft.%s.circuitbreaker.callsFailed.total", context); + default void incrementCircuitbreakerCallsFailedTotal() { + increment("ft.%s.circuitbreaker.callsFailed.total"); } - default void incrementCircuitbreakerCallsPreventedTotal(InvocationContext context) { - increment("ft.%s.circuitbreaker.callsPrevented.total", context); + default void incrementCircuitbreakerCallsPreventedTotal() { + increment("ft.%s.circuitbreaker.callsPrevented.total"); } - default void incrementCircuitbreakerOpenedTotal(InvocationContext context) { - increment("ft.%s.circuitbreaker.opened.total", context); + default void incrementCircuitbreakerOpenedTotal() { + increment("ft.%s.circuitbreaker.opened.total"); } - default void insertCircuitbreakerOpenTotal(LongSupplier gauge, InvocationContext context) { - insert("ft.%s.circuitbreaker.open.total", gauge, context); + default void insertCircuitbreakerOpenTotal(LongSupplier gauge) { + insert("ft.%s.circuitbreaker.open.total", gauge); } - default void insertCircuitbreakerHalfOpenTotal(LongSupplier gauge, InvocationContext context) { - insert("ft.%s.circuitbreaker.halfOpen.total", gauge, context); + default void insertCircuitbreakerHalfOpenTotal(LongSupplier gauge) { + insert("ft.%s.circuitbreaker.halfOpen.total", gauge); } - default void insertCircuitbreakerClosedTotal(LongSupplier gauge, InvocationContext context) { - insert("ft.%s.circuitbreaker.closed.total", gauge, context); + default void insertCircuitbreakerClosedTotal(LongSupplier gauge) { + insert("ft.%s.circuitbreaker.closed.total", gauge); } @@ -142,28 +140,28 @@ default void insertCircuitbreakerClosedTotal(LongSupplier gauge, InvocationConte * @Bulkhead */ - default void incrementBulkheadCallsAcceptedTotal(InvocationContext context) { - increment("ft.%s.bulkhead.callsAccepted.total", context); + default void incrementBulkheadCallsAcceptedTotal() { + increment("ft.%s.bulkhead.callsAccepted.total"); } - default void incrementBulkheadCallsRejectedTotal(InvocationContext context) { - increment("ft.%s.bulkhead.callsRejected.total", context); + default void incrementBulkheadCallsRejectedTotal() { + increment("ft.%s.bulkhead.callsRejected.total"); } - default void insertBulkheadConcurrentExecutions(LongSupplier gauge, InvocationContext context) { - insert("ft.%s.bulkhead.concurrentExecutions", gauge, context); + default void insertBulkheadConcurrentExecutions(LongSupplier gauge) { + insert("ft.%s.bulkhead.concurrentExecutions", gauge); } - default void insertBulkheadWaitingQueuePopulation(LongSupplier gauge, InvocationContext context) { - insert("ft.%s.bulkhead.waitingQueue.population", gauge, context); + default void insertBulkheadWaitingQueuePopulation(LongSupplier gauge) { + insert("ft.%s.bulkhead.waitingQueue.population", gauge); } - default void addBulkheadExecutionDuration(long duration, InvocationContext context) { - add("ft.%s.bulkhead.executionDuration", duration, context); + default void addBulkheadExecutionDuration(long duration) { + add("ft.%s.bulkhead.executionDuration", duration); } - default void addBulkheadWaitingDuration(long duration, InvocationContext context) { - add("ft.%s.bulkhead.waiting.duration", duration, context); + default void addBulkheadWaitingDuration(long duration) { + add("ft.%s.bulkhead.waiting.duration", duration); } @@ -171,7 +169,7 @@ default void addBulkheadWaitingDuration(long duration, InvocationContext context * @Fallback */ - default void incrementFallbackCallsTotal(InvocationContext context) { - increment("ft.%s.fallback.calls.total", context); + default void incrementFallbackCallsTotal() { + increment("ft.%s.fallback.calls.total"); } } diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/FaultToleranceService.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/FaultToleranceService.java index 7581e00f35e..c058eec1289 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/FaultToleranceService.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/FaultToleranceService.java @@ -1,372 +1,49 @@ -/* - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. - * - * Copyright (c) 2017-2018 Payara Foundation and/or its affiliates. All rights reserved. - * - * The contents of this file are subject to the terms of either the GNU - * General Public License Version 2 only ("GPL") or the Common Development - * and Distribution License("CDDL") (collectively, the "License"). You - * may not use this file except in compliance with the License. You can - * obtain a copy of the License at - * https://github.com/payara/Payara/blob/master/LICENSE.txt - * See the License for the specific - * language governing permissions and limitations under the License. - * - * When distributing the software, include this License Header Notice in each - * file and include the License file at glassfish/legal/LICENSE.txt. - * - * GPL Classpath Exception: - * The Payara Foundation designates this particular file as subject to the "Classpath" - * exception as provided by the Payara Foundation in the GPL Version 2 section of the License - * file that accompanied this code. - * - * Modifications: - * If applicable, add the following below the License Header, with the fields - * enclosed by brackets [] replaced by your own identifying information: - * "Portions Copyright [year] [name of copyright owner]" - * - * Contributor(s): - * If you wish your version of this file to be governed by only the CDDL or - * only the GPL Version 2, indicate your decision by adding "[Contributor] - * elects to include this software in this distribution under the [CDDL or GPL - * Version 2] license." If you don't indicate a single choice of license, a - * recipient has the option to distribute your version of this file under - * either the CDDL, the GPL Version 2 or to extend the choice of license to - * its licensees as provided above. However, if you add GPL Version 2 code - * and therefore, elected the GPL Version 2 license, then the option applies - * only if the new code is made subject to such option by the copyright - * holder. - */ package fish.payara.microprofile.faulttolerance; -import fish.payara.microprofile.faulttolerance.cdi.CdiFaultToleranceConfig; -import fish.payara.microprofile.faulttolerance.cdi.CdiFaultToleranceMetrics; -import fish.payara.microprofile.faulttolerance.cdi.FaultToleranceCdiUtils; -import fish.payara.microprofile.faulttolerance.cdi.FaultToleranceCdiUtils.Stereotypes; -import fish.payara.microprofile.faulttolerance.policy.AsynchronousPolicy; -import fish.payara.microprofile.faulttolerance.policy.FallbackPolicy; -import fish.payara.microprofile.faulttolerance.state.BulkheadSemaphore; -import fish.payara.microprofile.faulttolerance.state.CircuitBreakerState; -import fish.payara.notification.requesttracing.RequestTraceSpan; -import fish.payara.nucleus.requesttracing.RequestTracingService; - -import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; -import java.util.Arrays; -import java.util.Map; import java.util.concurrent.Callable; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; -import java.util.logging.Level; -import java.util.logging.Logger; -import javax.annotation.PostConstruct; -import javax.enterprise.concurrent.ManagedExecutorService; -import javax.enterprise.concurrent.ManagedScheduledExecutorService; -import javax.enterprise.inject.spi.CDI; -import javax.inject.Inject; -import javax.inject.Named; + import javax.interceptor.InvocationContext; -import javax.naming.InitialContext; -import javax.naming.NamingException; -import org.eclipse.microprofile.faulttolerance.Fallback; import org.eclipse.microprofile.faulttolerance.FallbackHandler; -import org.eclipse.microprofile.faulttolerance.exceptions.FaultToleranceDefinitionException; -import org.glassfish.api.StartupRunLevel; -import org.glassfish.api.admin.ServerEnvironment; -import org.glassfish.api.event.EventListener; -import org.glassfish.api.event.Events; -import org.glassfish.api.invocation.ComponentInvocation; -import org.glassfish.api.invocation.InvocationManager; -import org.glassfish.hk2.api.ServiceLocator; -import org.glassfish.hk2.runlevel.RunLevel; -import org.glassfish.internal.data.ApplicationInfo; -import org.glassfish.internal.data.ApplicationRegistry; -import org.glassfish.internal.deployment.Deployment; -import org.jvnet.hk2.annotations.ContractsProvided; -import org.jvnet.hk2.annotations.Optional; -import org.jvnet.hk2.annotations.Service; - -/** - * Base Service for MicroProfile Fault Tolerance. - * - * @author Andrew Pielage - */ -@ContractsProvided(FaultToleranceEnvironment.class) -@Service(name = "microprofile-fault-tolerance-service") -@RunLevel(StartupRunLevel.VAL) -public class FaultToleranceService implements EventListener, FaultToleranceEnvironment { - - private static final Logger logger = Logger.getLogger(FaultToleranceService.class.getName()); - - @Inject - @Named(ServerEnvironment.DEFAULT_INSTANCE_NAME) - @Optional - private FaultToleranceServiceConfiguration serviceConfig; - - private InvocationManager invocationManager; - - @Inject - private RequestTracingService requestTracingService; - @Inject - private ServiceLocator serviceLocator; - - @Inject - private Events events; - - private final Map stateByApplication = new ConcurrentHashMap<>(); - private ManagedScheduledExecutorService defaultScheduledExecutorService; - private ManagedExecutorService defaultExecutorService; - - @PostConstruct - public void postConstruct() throws NamingException { - events.register(this); - serviceConfig = serviceLocator.getService(FaultToleranceServiceConfiguration.class); - invocationManager = serviceLocator.getService(InvocationManager.class); - requestTracingService = serviceLocator.getService(RequestTracingService.class); - InitialContext context = new InitialContext(); - defaultExecutorService = (ManagedExecutorService) context.lookup("java:comp/DefaultManagedExecutorService"); - defaultScheduledExecutorService = (ManagedScheduledExecutorService) context - .lookup("java:comp/DefaultManagedScheduledExecutorService"); - } - - @Override - public void event(Event event) { - if (event.is(Deployment.APPLICATION_UNLOADED)) { - ApplicationInfo info = (ApplicationInfo) event.hook(); - deregisterApplication(info.getName()); - } - } - - @Override - public FaultToleranceConfig getConfig(InvocationContext context, Stereotypes stereotypes) { - FaultToleranceApplicationState appState = getApplicationState(getApplicationContext(context)); - return appState.getConfig() - .updateAndGet(config -> config != null ? config : new CdiFaultToleranceConfig(null, stereotypes)); - } - - @Override - public FaultToleranceMetrics getMetrics(InvocationContext context) { - FaultToleranceApplicationState appState = getApplicationState(getApplicationContext(context)); - return appState.getMetrics() - .updateAndGet(metrics -> metrics != null ? metrics : new CdiFaultToleranceMetrics(null)); - } - - //TODO use the scheduler to schedule a clean of FT Info +import fish.payara.microprofile.faulttolerance.service.Stereotypes; +import fish.payara.microprofile.faulttolerance.state.BulkheadSemaphore; +import fish.payara.microprofile.faulttolerance.state.CircuitBreakerState; - private ManagedExecutorService getManagedExecutorService() { - return lookup(serviceConfig.getManagedExecutorService(), defaultExecutorService); - } +public interface FaultToleranceService { - private ManagedScheduledExecutorService getManagedScheduledExecutorService() { - return lookup(serviceConfig.getManagedScheduledExecutorService(), defaultScheduledExecutorService); - } + /* + * Factory methods: + */ - @SuppressWarnings("unchecked") - private static T lookup(String name, T defaultInstance) { - // If no name has been set, just get the default - if (name == null || name.isEmpty()) { - return defaultInstance; - } - try { - return (T) new InitialContext().lookup(name); - } catch (Exception ex) { - logger.log(Level.INFO, "Could not find configured , " + name + ", so resorting to default", ex); - return defaultInstance; - } - } + FaultToleranceConfig getConfig(InvocationContext context, Stereotypes stereotypes); - private FaultToleranceApplicationState getApplicationState(String applicationName) { - return stateByApplication.computeIfAbsent(applicationName, key -> new FaultToleranceApplicationState()); - } + FaultToleranceMetrics getMetrics(InvocationContext context); - private BulkheadSemaphore getBulkheadExecutionSemaphore(String applicationName, Object invocationTarget, - Method annotatedMethod, int bulkheadValue) { - return getApplicationState(applicationName).getBulkheadExecutionSemaphores() - .computeIfAbsent(invocationTarget, key -> new ConcurrentHashMap<>()) - .computeIfAbsent( getFullMethodSignature(annotatedMethod), key -> new BulkheadSemaphore(bulkheadValue)); - } + CircuitBreakerState getState(int requestVolumeThreshold, InvocationContext context); - private BulkheadSemaphore getBulkheadExecutionQueueSemaphore(String applicationName, Object invocationTarget, - Method annotatedMethod, int bulkheadWaitingTaskQueue) { - return getApplicationState(applicationName).getBulkheadExecutionQueueSemaphores() - .computeIfAbsent(invocationTarget, key -> new ConcurrentHashMap<>()) - .computeIfAbsent( getFullMethodSignature(annotatedMethod), key -> new BulkheadSemaphore(bulkheadWaitingTaskQueue)); - } + BulkheadSemaphore getConcurrentExecutions(int maxConcurrentThreads, InvocationContext context); - private CircuitBreakerState getCircuitBreakerState(String applicationName, Object invocationTarget, - Method annotatedMethod, int requestVolumeThreshold) { - return getApplicationState(applicationName).getCircuitBreakerStates() - .computeIfAbsent(invocationTarget, key -> new ConcurrentHashMap<>()) - .computeIfAbsent(getFullMethodSignature(annotatedMethod), key -> new CircuitBreakerState(requestVolumeThreshold)); - } + BulkheadSemaphore getWaitingQueuePopulation(int queueCapacity, InvocationContext context); - /** - * Removes an application from the enabled map, CircuitBreaker map, and bulkhead maps - * @param applicationName The name of the application to remove - */ - private void deregisterApplication(String applicationName) { - stateByApplication.remove(applicationName); - } + void delay(long delayMillis, InvocationContext context) throws InterruptedException; - /** - * Gets the application name from the invocation manager. Failing that, it will use the module name, component name, - * or method signature (in that order). - * @param invocationManager The invocation manager to get the application name from - * @param context The context of the current invocation - * @return The application name - */ - private String getApplicationContext(InvocationContext context) { - ComponentInvocation currentInvocation = invocationManager.getCurrentInvocation(); - String appName = currentInvocation.getAppName(); - if (appName != null) { - return appName; - } - appName = currentInvocation.getModuleName(); - if (appName != null) { - return appName; - } - appName = currentInvocation.getComponentId(); - // If we've found a component name, check if there's an application registered with the same name - if (appName != null) { - // If it's not directly in the registry, it's possible due to how the componentId is constructed - if (serviceLocator.getService(ApplicationRegistry.class).get(appName) == null) { - // The application name should be the first component - return appName.split("_/")[0]; - } - } - // If we still don't have a name - just construct it from the method signature - return getFullMethodSignature(context.getMethod()); - } + void runAsynchronous(CompletableFuture asyncResult, Callable operation) throws Exception; /** - * Helper method to generate a full method signature consisting of canonical class name, method name, - * parameter types, and return type. - * @param annotatedMethod The annotated Method to generate the signature for - * @return A String in the format of CanonicalClassName#MethodName({ParameterTypes})>ReturnType - */ - private static String getFullMethodSignature(Method annotatedMethod) { - return annotatedMethod.getDeclaringClass().getCanonicalName() - + "#" + annotatedMethod.getName() - + "(" + Arrays.toString(annotatedMethod.getParameterTypes()) + ")" - + ">" + annotatedMethod.getReturnType().getSimpleName(); - } - - private void startFaultToleranceSpan(RequestTraceSpan span, InvocationContext invocationContext) { - if (requestTracingService != null && requestTracingService.isRequestTracingEnabled()) { - addGenericFaultToleranceRequestTracingDetails(span, invocationContext); - requestTracingService.startTrace(span); - } - } - - private void endFaultToleranceSpan() { - if (requestTracingService != null && requestTracingService.isRequestTracingEnabled()) { - requestTracingService.endTrace(); - } - } - - private void addGenericFaultToleranceRequestTracingDetails(RequestTraceSpan span, - InvocationContext invocationContext) { - span.addSpanTag("App Name", invocationManager.getCurrentInvocation().getAppName()); - span.addSpanTag("Component ID", invocationManager.getCurrentInvocation().getComponentId()); - span.addSpanTag("Module Name", invocationManager.getCurrentInvocation().getModuleName()); - span.addSpanTag("Class Name", invocationContext.getMethod().getDeclaringClass().getName()); - span.addSpanTag("Method Name", invocationContext.getMethod().getName()); - } - - - /* - * Execution + * @return A future that can be cancelled if the method execution completes before the interrupt happens */ + Future scheduleDelayed(long delayMillis, Runnable operation) throws Exception; - @Override - public CircuitBreakerState getState(int requestVolumeThreshold, InvocationContext context) { - return getCircuitBreakerState(getApplicationContext(context), context.getTarget(), - context.getMethod(), requestVolumeThreshold); - } - - @Override - public BulkheadSemaphore getConcurrentExecutions(int maxConcurrentThreads, InvocationContext context) { - return getBulkheadExecutionSemaphore(getApplicationContext(context), - context.getTarget(), context.getMethod(), maxConcurrentThreads); - } - - @Override - public BulkheadSemaphore getWaitingQueuePopulation(int queueCapacity, InvocationContext context) { - return getBulkheadExecutionQueueSemaphore(getApplicationContext(context), - context.getTarget(), context.getMethod(), queueCapacity); - } - - @Override - public void delay(long delayMillis, InvocationContext context) throws InterruptedException { - if (delayMillis <= 0) { - return; - } - startTrace("delayRetry", context); - try { - Thread.sleep(delayMillis); - } finally { - endTrace(); - } - } - - @Override - public void runAsynchronous(CompletableFuture asyncResult, Callable operation) throws Exception { - Runnable task = () -> { - if (!asyncResult.isCancelled() && !Thread.currentThread().isInterrupted()) { - try { - Future futureResult = AsynchronousPolicy.toFuture(operation.call()); - if (!asyncResult.isCancelled()) { // could be cancelled in the meanwhile - if (!asyncResult.isDone()) { - asyncResult.complete(futureResult.get()); - } - } else { - futureResult.cancel(true); - } - } catch (Exception ex) { - // Note that even ExecutionException is not unpacked (intentionally) - asyncResult.completeExceptionally(ex); - } - } - }; - getManagedExecutorService().submit(task); - } - - @Override - public Future scheduleDelayed(long delayMillis, Runnable operation) throws Exception { - return getManagedScheduledExecutorService().schedule(operation, delayMillis, TimeUnit.MILLISECONDS); - } + Object fallbackHandle(Class> fallbackClass, InvocationContext context, Exception exception) throws Exception; - @Override - public Object fallbackHandle(Class> fallbackClass, InvocationContext context, - Exception exception) throws Exception { - return CDI.current().select(fallbackClass).get() - .handle(new FaultToleranceExecutionContext(context.getMethod(), context.getParameters(), exception)); - } + Object fallbackInvoke(Method fallbackMethod, InvocationContext context) throws Exception; - @Override - public Object fallbackInvoke(Method fallbackMethod, InvocationContext context) throws Exception { - try { - fallbackMethod.setAccessible(true); - return fallbackMethod.invoke(context.getTarget(), context.getParameters()); - } catch (InvocationTargetException e) { - throw (Exception) e.getTargetException(); - } catch (IllegalAccessException e) { - throw new FaultToleranceDefinitionException(e); // should not happen as we validated - } - } + void startTrace(String method, InvocationContext context); - @Override - public void startTrace(String method, InvocationContext context) { - startFaultToleranceSpan(new RequestTraceSpan(method), context); - } + void endTrace(); - @Override - public void endTrace() { - endFaultToleranceSpan(); - } } diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/cdi/CdiFaultToleranceConfig.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/cdi/CdiFaultToleranceConfig.java deleted file mode 100644 index 7be81c9f7d1..00000000000 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/cdi/CdiFaultToleranceConfig.java +++ /dev/null @@ -1,297 +0,0 @@ -package fish.payara.microprofile.faulttolerance.cdi; - -import java.io.Serializable; -import java.lang.annotation.Annotation; -import java.time.temporal.ChronoUnit; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicReference; -import java.util.logging.Level; -import java.util.logging.Logger; - -import javax.interceptor.Interceptor; -import javax.interceptor.InvocationContext; - -import org.eclipse.microprofile.config.Config; -import org.eclipse.microprofile.config.ConfigProvider; -import org.eclipse.microprofile.faulttolerance.Bulkhead; -import org.eclipse.microprofile.faulttolerance.CircuitBreaker; -import org.eclipse.microprofile.faulttolerance.Fallback; -import org.eclipse.microprofile.faulttolerance.FallbackHandler; -import org.eclipse.microprofile.faulttolerance.Retry; -import org.eclipse.microprofile.faulttolerance.Timeout; - -import fish.payara.microprofile.faulttolerance.FaultToleranceConfig; -import fish.payara.microprofile.faulttolerance.cdi.FaultToleranceCdiUtils.Stereotypes; - -/** - * A {@link FaultToleranceConfig} using {@link Config} to resolve overrides. - * The {@link Config} is resolved using the {@link ConfigProvider} if needed. - * - * @author Jan Bernitt - */ -public class CdiFaultToleranceConfig implements FaultToleranceConfig, Serializable { - - private static final String NON_FALLBACK_ENABLED_PROPERTY = "MP_Fault_Tolerance_NonFallback_Enabled"; - private static final String METRICS_ENABLED_PROPERTY = "MP_Fault_Tolerance_Metrics_Enabled"; - private static final String INTERCEPTOR_PRIORITY_PROPERTY = "mp.fault.tolerance.interceptor.priority"; - - private static final Logger logger = Logger.getLogger(CdiFaultToleranceConfig.class.getName()); - - /** - * These tree properties should only be read once at the start of the application, therefore they are cached in - * static field. - */ - private final AtomicReference nonFallbackEnabled = new AtomicReference<>(); - private final AtomicReference metricsEnabled = new AtomicReference<>(); - private final AtomicInteger interceptorPriority = new AtomicInteger(-1); - - private final Stereotypes sterotypes; - private transient Config config; - - public CdiFaultToleranceConfig(Config config, Stereotypes sterotypes) { - this.sterotypes = sterotypes; - this.config = config; - } - - private Config getConfig() { - if (config == null) { - logger.log(Level.INFO, "Resolving Fault Tolerance Config from Provider."); - try { - config = ConfigProvider.getConfig(); - } catch (IllegalArgumentException ex) { - logger.log(Level.INFO, "No config could be found", ex); - } - } - return config; - } - - - /* - * General - */ - - @Override - public boolean isNonFallbackEnabled(InvocationContext context) { - if (nonFallbackEnabled.get() == null) { - nonFallbackEnabled.compareAndSet(null, - getConfig().getOptionalValue(NON_FALLBACK_ENABLED_PROPERTY, Boolean.class).orElse(true)); - } - return nonFallbackEnabled.get().booleanValue(); - } - - @Override - public boolean isMetricsEnabled(InvocationContext context) { - if (metricsEnabled.get() == null) { - metricsEnabled.compareAndSet(null, - getConfig().getOptionalValue(METRICS_ENABLED_PROPERTY, Boolean.class).orElse(true)); - } - return metricsEnabled.get().booleanValue(); - } - - @Override - public boolean isEnabled(Class annotationType, InvocationContext context) { - return FaultToleranceCdiUtils.getEnabledOverrideValue(getConfig(), annotationType, context, - annotationType == Fallback.class || isNonFallbackEnabled(context)); - } - - @Override - public A getAnnotation(Class annotationType, InvocationContext context) { - return FaultToleranceCdiUtils.getAnnotation(sterotypes, annotationType, context); - } - - @Override - public int interceptorPriority() { - return interceptorPriority.updateAndGet(priority -> priority > 0 ? priority - : getConfig().getOptionalValue(INTERCEPTOR_PRIORITY_PROPERTY, Integer.class) - .orElse(Interceptor.Priority.PLATFORM_AFTER + 15)); - } - - /* - * Retry - */ - - @Override - public int maxRetries(Retry annotation, InvocationContext context) { - return intValue(Retry.class, "maxRetries", context, annotation.maxRetries()); - } - - @Override - public long delay(Retry annotation, InvocationContext context) { - return longValue(Retry.class, "delay", context, annotation.delay()); - } - - @Override - public ChronoUnit delayUnit(Retry annotation, InvocationContext context) { - return chronoUnitValue(Retry.class, "delayUnit", context, annotation.delayUnit()); - } - - @Override - public long maxDuration(Retry annotation, InvocationContext context) { - return longValue(Retry.class, "maxDuration", context, annotation.maxDuration()); - } - - @Override - public ChronoUnit durationUnit(Retry annotation, InvocationContext context) { - return chronoUnitValue(Retry.class, "durationUnit", context, annotation.durationUnit()); - } - - @Override - public long jitter(Retry annotation, InvocationContext context) { - return longValue(Retry.class, "jitter", context, annotation.jitter()); - } - - @Override - public ChronoUnit jitterDelayUnit(Retry annotation, InvocationContext context) { - return chronoUnitValue(Retry.class, "jitterDelayUnit", context, annotation.jitterDelayUnit()); - } - - @Override - public Class[] retryOn(Retry annotation, InvocationContext context) { - return getClassArrayValue(Retry.class, "retryOn", context, annotation.retryOn()); - } - - @Override - public Class[] abortOn(Retry annotation, InvocationContext context) { - return getClassArrayValue(Retry.class, "abortOn", context, annotation.abortOn()); - } - - - /* - * Circuit-Breaker - */ - - @Override - public Class[] failOn(CircuitBreaker annotation, InvocationContext context) { - return getClassArrayValue(CircuitBreaker.class, "failOn", context, annotation.failOn()); - } - - @Override - public long delay(CircuitBreaker annotation, InvocationContext context) { - return longValue(CircuitBreaker.class, "delay", context, annotation.delay()); - } - - @Override - public ChronoUnit delayUnit(CircuitBreaker annotation, InvocationContext context) { - return chronoUnitValue(CircuitBreaker.class, "delayUnit", context, annotation.delayUnit()); - } - - @Override - public int requestVolumeThreshold(CircuitBreaker annotation, InvocationContext context) { - return intValue(CircuitBreaker.class, "requestVolumeThreshold", context, annotation.requestVolumeThreshold()); - } - - @Override - public double failureRatio(CircuitBreaker annotation, InvocationContext context) { - return value(CircuitBreaker.class, "failureRatio", context, Double.class, annotation.failureRatio()); - } - - @Override - public int successThreshold(CircuitBreaker annotation, InvocationContext context) { - return intValue(CircuitBreaker.class, "successThreshold", context, annotation.successThreshold()); - } - - - /* - * Bulkhead - */ - - @Override - public int value(Bulkhead annotation, InvocationContext context) { - return intValue(Bulkhead.class, "value", context, annotation.value()); - } - - @Override - public int waitingTaskQueue(Bulkhead annotation, InvocationContext context) { - return intValue(Bulkhead.class, "waitingTaskQueue", context, annotation.waitingTaskQueue()); - } - - - /* - * Timeout - */ - - @Override - public long value(Timeout annotation, InvocationContext context) { - return longValue(Timeout.class, "value", context, annotation.value()); - } - - @Override - public ChronoUnit unit(Timeout annotation, InvocationContext context) { - return chronoUnitValue(Timeout.class, "unit", context, annotation.unit()); - } - - - /* - * Fallback - */ - - @SuppressWarnings("unchecked") - @Override - public Class> value(Fallback annotation, InvocationContext context) { - String className = FaultToleranceCdiUtils.getOverrideValue(getConfig(), Fallback.class, "value", - context, String.class, null); - if (className == null) { - return annotation.value(); - } - try { - return (Class>) Thread.currentThread().getContextClassLoader() - .loadClass(className); - } catch (ClassNotFoundException e) { - return annotation.value(); - } - } - - @Override - public String fallbackMethod(Fallback annotation, InvocationContext context) { - return value(Fallback.class, "fallbackMethod", context, String.class, annotation.fallbackMethod()); - } - - - /* - * Helpers - */ - - private long longValue(Class annotationType, String attribute, InvocationContext context, - long annotationValue) { - return value(annotationType, attribute, context, Long.class, annotationValue); - } - - private int intValue(Class annotationType, String attribute, InvocationContext context, - int annotationValue) { - return value(annotationType, attribute, context, Integer.class, annotationValue); - } - - private ChronoUnit chronoUnitValue(Class annotationType, String attribute, - InvocationContext context, ChronoUnit annotationValue) { - return value(annotationType, attribute, context, ChronoUnit.class, annotationValue); - } - - private T value(Class annotationType, String attribute, - InvocationContext context, Class valueType, T annotationValue) { - return FaultToleranceCdiUtils.getOverrideValue(getConfig(), annotationType, attribute, context, valueType, annotationValue); - } - - private Class[] getClassArrayValue(Class annotationType, - String attributeName, InvocationContext context, Class[] annotationValue) { - String classNames = FaultToleranceCdiUtils.getOverrideValue(getConfig(), annotationType, attributeName, context, - String.class, null); - if (classNames == null) { - return annotationValue; - } - try { - List> classList = new ArrayList<>(); - // Remove any curly or square brackets from the string, as well as any spaces and ".class"es - for (String className : classNames.replaceAll("[\\{\\[ \\]\\}]", "").replaceAll("\\.class", "") - .split(",")) { - classList.add(Class.forName(className)); - } - return classList.toArray(annotationValue); - } catch (ClassNotFoundException cnfe) { - logger.log(Level.INFO, "Could not find class from " + attributeName + " config, defaulting to annotation. " - + "Make sure you give the full canonical class name.", cnfe); - return annotationValue; - } - } -} diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/cdi/CdiFaultToleranceMetrics.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/cdi/CdiFaultToleranceMetrics.java deleted file mode 100644 index 473c8e04a22..00000000000 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/cdi/CdiFaultToleranceMetrics.java +++ /dev/null @@ -1,56 +0,0 @@ -package fish.payara.microprofile.faulttolerance.cdi; - -import java.util.function.LongSupplier; - -import javax.enterprise.inject.spi.CDI; -import javax.interceptor.InvocationContext; - -import org.eclipse.microprofile.metrics.Gauge; -import org.eclipse.microprofile.metrics.MetricRegistry; - -import fish.payara.microprofile.faulttolerance.FaultToleranceMetrics; - -/** - * A {@link FaultToleranceMetrics} service that uses {@link CDI} to resolve the {@link MetricRegistry} if needed. - * - * @author Jan Bernitt - */ -public class CdiFaultToleranceMetrics implements FaultToleranceMetrics { - - private MetricRegistry metricRegistry; - - public CdiFaultToleranceMetrics(MetricRegistry metricRegistry) { - this.metricRegistry = metricRegistry; - } - - @Override - public void increment(String keyPattern, InvocationContext context) { - getMetricRegistry().counter(metricName(keyPattern, context)).inc(); - } - - @Override - public void add(String keyPattern, long duration, InvocationContext context) { - getMetricRegistry().histogram(metricName(keyPattern, context)).update(duration); - } - - @Override - public void insert(String keyPattern, LongSupplier gauge, InvocationContext context) { - String metricName = metricName(keyPattern, context); - Gauge existingGauge = getMetricRegistry().getGauges().get(metricName); - if (existingGauge == null) { - Gauge newGauge = gauge::getAsLong; - getMetricRegistry().register(metricName, newGauge); - } - } - - private static String metricName(String keyPattern, InvocationContext context) { - return String.format(keyPattern, FaultToleranceCdiUtils.getCanonicalMethodName(context)); - } - - private MetricRegistry getMetricRegistry() { - if (metricRegistry == null) { - metricRegistry = CDI.current().select(MetricRegistry.class).get(); - } - return metricRegistry; - } -} diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/cdi/FaultToleranceCDIExtension.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/cdi/FaultToleranceCDIExtension.java index b179cd36c2b..e84b3bf30eb 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/cdi/FaultToleranceCDIExtension.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/cdi/FaultToleranceCDIExtension.java @@ -42,12 +42,12 @@ import fish.payara.microprofile.faulttolerance.interceptors.FaultToleranceBehaviour; import fish.payara.microprofile.faulttolerance.interceptors.FaultToleranceInterceptor; import fish.payara.microprofile.faulttolerance.policy.FaultTolerancePolicy; +import fish.payara.microprofile.faulttolerance.service.FaultToleranceUtils; import java.lang.annotation.Annotation; import java.lang.reflect.Method; import javax.enterprise.event.Observes; -import javax.enterprise.inject.spi.Annotated; import javax.enterprise.inject.spi.BeanManager; import javax.enterprise.inject.spi.BeforeBeanDiscovery; import javax.enterprise.inject.spi.Extension; @@ -93,21 +93,15 @@ void processAnnotatedType(@Observes @WithAnnotations({ Asynchronous.class, B * @param processAnnotatedType type currently processed */ private static void validateAndMark(ProcessAnnotatedType processAnnotatedType) { - boolean markAllMethods = isAnnotaetdWithFaultToleranceAnnotations(processAnnotatedType.getAnnotatedType()); + boolean markAllMethods = FaultToleranceUtils + .isAnnotaetdWithFaultToleranceAnnotations(processAnnotatedType.getAnnotatedType()); + Class targetClass = processAnnotatedType.getAnnotatedType().getJavaClass(); for (AnnotatedMethodConfigurator methodConfigurator : processAnnotatedType.configureAnnotatedType().methods()) { - if (markAllMethods || isAnnotaetdWithFaultToleranceAnnotations(methodConfigurator.getAnnotated())) { - FaultTolerancePolicy.asAnnotated(methodConfigurator.getAnnotated().getJavaMember()); + if (markAllMethods || FaultToleranceUtils + .isAnnotaetdWithFaultToleranceAnnotations(methodConfigurator.getAnnotated())) { + FaultTolerancePolicy.asAnnotated(targetClass, methodConfigurator.getAnnotated().getJavaMember()); methodConfigurator.add(MARKER); } } } - - private static boolean isAnnotaetdWithFaultToleranceAnnotations(Annotated element) { - return element.isAnnotationPresent(Asynchronous.class) - || element.isAnnotationPresent(Bulkhead.class) - || element.isAnnotationPresent(CircuitBreaker.class) - || element.isAnnotationPresent(Fallback.class) - || element.isAnnotationPresent(Retry.class) - || element.isAnnotationPresent(Timeout.class); - } } diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/AsynchronousInterceptor.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/AsynchronousInterceptor.java index ce68c85ebca..5e5e149d55d 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/AsynchronousInterceptor.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/AsynchronousInterceptor.java @@ -73,7 +73,7 @@ public Object intercept(InvocationContext context) throws Exception { try { // Attempt to proceed the InvocationContext with Asynchronous semantics if Fault Tolerance is enabled for // this method - if (getConfig().isNonFallbackEnabled(context) && getConfig().isEnabled(Asynchronous.class, context)) { + if (getConfig().isNonFallbackEnabled() && getConfig().isEnabled(Asynchronous.class)) { resultValue = asynchronous(context); } else { // If fault tolerance isn't enabled, just proceed as normal @@ -83,11 +83,11 @@ public Object intercept(InvocationContext context) throws Exception { } catch (Exception ex) { // If an exception was thrown, check if the method is annotated with @Fallback // We should only get here if executing synchronously, as the exception wouldn't get thrown in this thread - Fallback fallback = getConfig().getAnnotation(Fallback.class, context); + Fallback fallback = getConfig().getAnnotation(Fallback.class); // If the method was annotated with Fallback and the annotation is enabled, attempt it, otherwise just // propagate the exception upwards - if (fallback != null && getConfig().isEnabled(Fallback.class, context)) { + if (fallback != null && getConfig().isEnabled(Fallback.class)) { logger.log(Level.FINE, "Fallback annotation found on method - falling back from Asynchronous"); //FallbackPolicy fallbackPolicy = new FallbackPolicy(fallback, getConfig(), getExecution(), getMetrics(), context); resultValue = null; // fallbackPolicy.fallback(context, ex); diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/BaseFaultToleranceInterceptor.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/BaseFaultToleranceInterceptor.java index 0020f1c2080..10a6dac6cb8 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/BaseFaultToleranceInterceptor.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/BaseFaultToleranceInterceptor.java @@ -14,7 +14,7 @@ import fish.payara.microprofile.faulttolerance.FaultToleranceConfig; import fish.payara.microprofile.faulttolerance.FaultToleranceMetrics; -import fish.payara.microprofile.faulttolerance.FaultToleranceEnvironment; +import fish.payara.microprofile.faulttolerance.FaultToleranceService; import fish.payara.microprofile.faulttolerance.interceptors.fallback.FallbackPolicy; public abstract class BaseFaultToleranceInterceptor { @@ -23,8 +23,8 @@ public abstract class BaseFaultToleranceInterceptor { private final String type; private final Class annotationType; private final boolean throwExceptionForRetry; - private FaultToleranceConfig config = FaultToleranceConfig.ANNOTATED; - private FaultToleranceEnvironment execution; + private FaultToleranceConfig config; + private FaultToleranceService execution; private FaultToleranceMetrics metrics; protected BaseFaultToleranceInterceptor(Class annotationType, boolean throwExceptionForRetry) { @@ -46,7 +46,7 @@ public FaultToleranceMetrics getMetrics() { return metrics; } - public FaultToleranceEnvironment getExecution() { + public FaultToleranceService getExecution() { return execution; } @@ -54,43 +54,6 @@ public FaultToleranceEnvironment getExecution() { public Object intercept(InvocationContext context) throws Exception { Object resultValue = null; - try { - // Attempt to proceed the InvocationContext with FT semantics if FT is enabled for this method - if (getConfig().isNonFallbackEnabled(context) && getConfig().isEnabled(annotationType, context)) { - // Only increment the invocations metric if the Retry, Bulkhead, and CircuitBreaker annotations aren't present - if (!getConfig().isAnnotationPresent(Bulkhead.class, context) - && !getConfig().isAnnotationPresent(Retry.class, context) - && !getConfig().isAnnotationPresent(CircuitBreaker.class, context)) { - getMetrics().incrementInvocationsTotal(context); - } - - logger.log(Level.FINER, "Proceeding invocation with " + type + " semantics"); - resultValue = null; //TODO call FT specific method - } else { - // If fault tolerance isn't enabled, just proceed as normal - logger.log(Level.FINE, "Fault Tolerance not enabled, proceeding normally without " - + type + "."); - resultValue = context.proceed(); - } - } catch (Exception ex) { - if (throwExceptionForRetry && getConfig().isAnnotationPresent(Retry.class, context)) { - logger.log(Level.FINE, "Retry annotation found on method, propagating error upwards."); - throw ex; - } - Fallback fallback = getConfig().getAnnotation(Fallback.class, context); - - // Only fall back if the annotation hasn't been disabled - if (fallback != null && getConfig().isEnabled(Fallback.class, context)) { - logger.log(Level.FINE, "Fallback annotation found on method, and no Retry annotation - " - + "falling back from " + type); - FallbackPolicy fallbackPolicy = new FallbackPolicy(fallback, getConfig(), getExecution(), getMetrics(), context); - resultValue = fallbackPolicy.fallback(context, ex); - } else { - // Increment the failure counter metric - getMetrics().incrementInvocationsFailedTotal(context); - throw ex; - } - } return resultValue; } } diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/BulkheadInterceptor.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/BulkheadInterceptor.java index d096f866bdc..918501797f1 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/BulkheadInterceptor.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/BulkheadInterceptor.java @@ -77,11 +77,11 @@ public Object intercept(InvocationContext context) throws Exception { try { // Attempt to proceed the InvocationContext with Asynchronous semantics if Fault Tolerance is enabled for this // method - if (getConfig().isNonFallbackEnabled(context) && getConfig().isEnabled(Bulkhead.class, context)) { - if (getConfig().isMetricsEnabled(context)) { + if (getConfig().isNonFallbackEnabled() && getConfig().isEnabled(Bulkhead.class)) { + if (getConfig().isMetricsEnabled()) { // Only increment the invocations metric if the Retry annotation isn't present - if (getConfig().getAnnotation(Retry.class, context) == null) { - getMetrics().incrementInvocationsTotal(context); + if (getConfig().getAnnotation(Retry.class) == null) { + getMetrics().incrementInvocationsTotal(); } } @@ -94,18 +94,18 @@ public Object intercept(InvocationContext context) throws Exception { resultValue = context.proceed(); } } catch (Exception ex) { - Retry retry = getConfig().getAnnotation(Retry.class, context); + Retry retry = getConfig().getAnnotation(Retry.class); if (retry != null) { logger.log(Level.FINE, "Retry annotation found on method, propagating error upwards."); throw ex; } // If an exception was thrown, check if the method is annotated with @Fallback - Fallback fallback = getConfig().getAnnotation(Fallback.class, context); + Fallback fallback = getConfig().getAnnotation(Fallback.class); // If the method was annotated with Fallback and the annotation is enabled, attempt it, otherwise just // propagate the exception upwards - if (fallback != null && getConfig().isEnabled(Fallback.class, context)) { + if (fallback != null && getConfig().isEnabled(Fallback.class)) { logger.log(Level.FINE, "Fallback annotation found on method, and no Retry annotation - " + "falling back from Bulkhead"); FallbackPolicy fallbackPolicy = new FallbackPolicy(fallback, getConfig(), getExecution(), getMetrics(), context); @@ -114,7 +114,7 @@ public Object intercept(InvocationContext context) throws Exception { logger.log(Level.FINE, "Fallback annotation not found on method, propagating error upwards.", ex); // Increment the failure counter metric - getMetrics().incrementInvocationsFailedTotal(context); + getMetrics().incrementInvocationsFailedTotal(); throw ex; } } @@ -131,20 +131,20 @@ public Object intercept(InvocationContext context) throws Exception { private Object bulkhead(InvocationContext context) throws Exception { Object resultValue = null; - Bulkhead bulkhead = getConfig().getAnnotation(Bulkhead.class, context); + Bulkhead bulkhead = getConfig().getAnnotation(Bulkhead.class); - BulkheadSemaphore bulkheadExecutionSemaphore = getExecution().getConcurrentExecutions(getConfig().value(bulkhead, context), context); + BulkheadSemaphore bulkheadExecutionSemaphore = getExecution().getConcurrentExecutions(getConfig().value(bulkhead), context); - if (getConfig().isMetricsEnabled(context)) { - getMetrics().insertBulkheadConcurrentExecutions(bulkheadExecutionSemaphore::acquiredPermits, context); + if (getConfig().isMetricsEnabled()) { + getMetrics().insertBulkheadConcurrentExecutions(bulkheadExecutionSemaphore::acquiredPermits); } // If the Asynchronous annotation is present, use threadpool style, otherwise use semaphore style - if (getConfig().getAnnotation(Asynchronous.class, context) != null) { - BulkheadSemaphore bulkheadExecutionQueueSemaphore = getExecution().getWaitingQueuePopulation(getConfig().waitingTaskQueue(bulkhead, context), context); + if (getConfig().getAnnotation(Asynchronous.class) != null) { + BulkheadSemaphore bulkheadExecutionQueueSemaphore = getExecution().getWaitingQueuePopulation(getConfig().waitingTaskQueue(bulkhead), context); - if (getConfig().isMetricsEnabled(context)) { - getMetrics().insertBulkheadWaitingQueuePopulation(bulkheadExecutionQueueSemaphore::acquiredPermits, context); + if (getConfig().isMetricsEnabled()) { + getMetrics().insertBulkheadWaitingQueuePopulation(bulkheadExecutionQueueSemaphore::acquiredPermits); } // Start measuring the queue duration for MP Metrics @@ -168,7 +168,7 @@ private Object bulkhead(InvocationContext context) throws Exception { getExecution().endTrace(); // Record the queue time for MP Metrics - getMetrics().addBulkheadWaitingDuration(System.nanoTime() - queueStartTime, context); + getMetrics().addBulkheadWaitingDuration(System.nanoTime() - queueStartTime); } logger.log(Level.FINER, "Acquired bulkhead queue semaphore."); @@ -177,7 +177,7 @@ private Object bulkhead(InvocationContext context) throws Exception { bulkheadExecutionQueueSemaphore.release(); // Incremement the MP Metrics callsAccepted counter - getMetrics().incrementBulkheadCallsAcceptedTotal(context); + getMetrics().incrementBulkheadCallsAcceptedTotal(); // Start measuring the execution duration for MP Metrics long executionStartTime = System.nanoTime(); @@ -194,36 +194,36 @@ private Object bulkhead(InvocationContext context) throws Exception { bulkheadExecutionQueueSemaphore.release(); // Record the execution time for MP Metrics - getMetrics().addBulkheadExecutionDuration(System.nanoTime() - executionStartTime, context); + getMetrics().addBulkheadExecutionDuration(System.nanoTime() - executionStartTime); // Let the exception propagate further up - we just want to release the semaphores throw ex; } // Record the execution time for MP Metrics - getMetrics().addBulkheadExecutionDuration(System.nanoTime() - executionStartTime, context); + getMetrics().addBulkheadExecutionDuration(System.nanoTime() - executionStartTime); // Release the execution permit bulkheadExecutionSemaphore.release(); } catch (InterruptedException ex) { // Incremement the MP Metrics callsRejected counter - getMetrics().incrementBulkheadCallsRejectedTotal(context); + getMetrics().incrementBulkheadCallsRejectedTotal(); logger.log(Level.INFO, "Interrupted acquiring bulkhead semaphore", ex); throw new BulkheadException(ex); } } else { // Incremement the MP Metrics callsRejected counter - getMetrics().incrementBulkheadCallsRejectedTotal(context); + getMetrics().incrementBulkheadCallsRejectedTotal(); throw new BulkheadException("No free work or queue permits."); } } else { // Incremement the MP Metrics callsAccepted counter - getMetrics().incrementBulkheadCallsAcceptedTotal(context); + getMetrics().incrementBulkheadCallsAcceptedTotal(); // Record the queue time for MP Metrics - getMetrics().addBulkheadWaitingDuration(System.nanoTime() - queueStartTime, context); + getMetrics().addBulkheadWaitingDuration(System.nanoTime() - queueStartTime); // Start measuring the execution duration for MP Metrics long executionStartTime = System.nanoTime(); @@ -239,14 +239,14 @@ private Object bulkhead(InvocationContext context) throws Exception { bulkheadExecutionSemaphore.release(); // Record the execution time for MP Metrics - getMetrics().addBulkheadExecutionDuration(System.nanoTime() - executionStartTime, context); + getMetrics().addBulkheadExecutionDuration(System.nanoTime() - executionStartTime); // Let the exception propagate further up - we just want to release the semaphores throw ex; } // Record the execution time for MP Metrics - getMetrics().addBulkheadExecutionDuration(System.nanoTime() - executionStartTime, context); + getMetrics().addBulkheadExecutionDuration(System.nanoTime() - executionStartTime); // Release the permit bulkheadExecutionSemaphore.release(); @@ -255,7 +255,7 @@ private Object bulkhead(InvocationContext context) throws Exception { // Try to get an execution permit if (bulkheadExecutionSemaphore.tryAcquire(0, TimeUnit.SECONDS)) { // Incremement the MP Metrics callsAccepted counter - getMetrics().incrementBulkheadCallsAcceptedTotal(context); + getMetrics().incrementBulkheadCallsAcceptedTotal(); // Start measuring the execution duration for MP Metrics long executionStartTime = System.nanoTime(); @@ -271,20 +271,20 @@ private Object bulkhead(InvocationContext context) throws Exception { bulkheadExecutionSemaphore.release(); // Record the execution time for MP Metrics - getMetrics().addBulkheadExecutionDuration(System.nanoTime() - executionStartTime, context); + getMetrics().addBulkheadExecutionDuration(System.nanoTime() - executionStartTime); // Let the exception propagate further up - we just want to release the semaphores throw ex; } // Record the execution time for MP Metrics - getMetrics().addBulkheadExecutionDuration(System.nanoTime() - executionStartTime, context); + getMetrics().addBulkheadExecutionDuration(System.nanoTime() - executionStartTime); // Release the permit bulkheadExecutionSemaphore.release(); } else { // Incremement the MP Metrics callsRejected counter - getMetrics().incrementBulkheadCallsRejectedTotal(context); + getMetrics().incrementBulkheadCallsRejectedTotal(); throw new BulkheadException("No free work permits."); } diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/CircuitBreakerInterceptor.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/CircuitBreakerInterceptor.java index 0ce9b5c96c1..e2464f96922 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/CircuitBreakerInterceptor.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/CircuitBreakerInterceptor.java @@ -76,11 +76,11 @@ public Object intercept(InvocationContext context) throws Exception { // Attempt to proceed the invocation with CircuitBreaker semantics if Fault Tolerance is enabled for this method try { // Attempt to proceed the InvocationContext with Asynchronous semantics if Fault Tolerance is enabled - if (getConfig().isNonFallbackEnabled(context) && getConfig().isEnabled(CircuitBreaker.class, context)) { + if (getConfig().isNonFallbackEnabled() && getConfig().isEnabled(CircuitBreaker.class)) { // Only increment the invocations metric if the Retry and Bulkhead annotations aren't present - if (getConfig().getAnnotation(Bulkhead.class, context) == null - && getConfig().getAnnotation(Retry.class, context) == null) { - getMetrics().incrementInvocationsTotal(context); + if (getConfig().getAnnotation(Bulkhead.class) == null + && getConfig().getAnnotation(Retry.class) == null) { + getMetrics().incrementInvocationsTotal(); } logger.log(Level.FINER, "Proceeding invocation with circuitbreaker semantics"); @@ -91,23 +91,23 @@ && getConfig().getAnnotation(Retry.class, context) == null) { resultValue = context.proceed(); } } catch (Exception ex) { - Retry retry = getConfig().getAnnotation(Retry.class, context); + Retry retry = getConfig().getAnnotation(Retry.class); if (retry != null) { logger.log(Level.FINE, "Retry annotation found on method, propagating error upwards."); throw ex; } - Fallback fallback = getConfig().getAnnotation(Fallback.class, context); + Fallback fallback = getConfig().getAnnotation(Fallback.class); // Only fall back if the annotation hasn't been disabled - if (fallback != null && getConfig().isEnabled(Fallback.class, context)) { + if (fallback != null && getConfig().isEnabled(Fallback.class)) { logger.log(Level.FINE, "Fallback annotation found on method, and no Retry annotation - " + "falling back from CircuitBreaker"); FallbackPolicy fallbackPolicy = new FallbackPolicy(fallback, getConfig(), getExecution(), getMetrics(), context); resultValue = fallbackPolicy.fallback(context, ex); } else { // Increment the failure counter metric - getMetrics().incrementInvocationsFailedTotal(context); + getMetrics().incrementInvocationsFailedTotal(); throw ex; } } @@ -118,29 +118,29 @@ && getConfig().getAnnotation(Retry.class, context) == null) { private Object circuitBreak(InvocationContext context) throws Exception { Object resultValue = null; - CircuitBreaker circuitBreaker = getConfig().getAnnotation(CircuitBreaker.class, context); + CircuitBreaker circuitBreaker = getConfig().getAnnotation(CircuitBreaker.class); - Class[] failOn = getConfig().failOn(circuitBreaker, context); - long delay = getConfig().delay(circuitBreaker, context); - ChronoUnit delayUnit = getConfig().delayUnit(circuitBreaker, context); - int requestVolumeThreshold = getConfig().requestVolumeThreshold(circuitBreaker, context); - double failureRatio = getConfig().failureRatio(circuitBreaker, context); - int successThreshold = getConfig().successThreshold(circuitBreaker, context); + Class[] failOn = getConfig().failOn(circuitBreaker); + long delay = getConfig().delay(circuitBreaker); + ChronoUnit delayUnit = getConfig().delayUnit(circuitBreaker); + int requestVolumeThreshold = getConfig().requestVolumeThreshold(circuitBreaker); + double failureRatio = getConfig().failureRatio(circuitBreaker); + int successThreshold = getConfig().successThreshold(circuitBreaker); long delayMillis = Duration.of(delay, delayUnit).toMillis(); CircuitBreakerState circuitBreakerState = getExecution().getState(requestVolumeThreshold, context); - if (getConfig().isMetricsEnabled(context)) { - getMetrics().insertCircuitbreakerOpenTotal(circuitBreakerState::nanosOpen, context); - getMetrics().insertCircuitbreakerHalfOpenTotal(circuitBreakerState::nanosHalfOpen, context); - getMetrics().insertCircuitbreakerClosedTotal(circuitBreakerState::nanosClosed, context); + if (getConfig().isMetricsEnabled()) { + getMetrics().insertCircuitbreakerOpenTotal(circuitBreakerState::nanosOpen); + getMetrics().insertCircuitbreakerHalfOpenTotal(circuitBreakerState::nanosHalfOpen); + getMetrics().insertCircuitbreakerClosedTotal(circuitBreakerState::nanosClosed); } switch (circuitBreakerState.getCircuitState()) { case OPEN: logger.log(Level.FINER, "CircuitBreaker is Open, throwing exception"); - getMetrics().incrementCircuitbreakerCallsPreventedTotal(context); + getMetrics().incrementCircuitbreakerCallsPreventedTotal(); // If open, immediately throw an error throw new CircuitBreakerOpenException("CircuitBreaker for method " @@ -159,7 +159,7 @@ private Object circuitBreak(InvocationContext context) throws Exception { + "recording failure against CircuitBreaker"); // Add a failure result to the queue circuitBreakerState.recordClosedResult(Boolean.FALSE); - getMetrics().incrementCircuitbreakerCallsFailedTotal(context); + getMetrics().incrementCircuitbreakerCallsFailedTotal(); // Calculate the failure ratio, and if we're over it, open the circuit breakCircuitIfRequired( @@ -173,7 +173,7 @@ private Object circuitBreak(InvocationContext context) throws Exception { // If everything is bon, add a success value circuitBreakerState.recordClosedResult(Boolean.TRUE); - getMetrics().incrementCircuitbreakerCallsSucceededTotal(context); + getMetrics().incrementCircuitbreakerCallsSucceededTotal(); // Calculate the failure ratio, and if we're over it, open the circuit breakCircuitIfRequired( @@ -193,7 +193,7 @@ private Object circuitBreak(InvocationContext context) throws Exception { logger.log(Level.FINE, "Caught exception is included in CircuitBreaker failOn, " + "reopening half open circuit"); - getMetrics().incrementCircuitbreakerCallsFailedTotal(context); + getMetrics().incrementCircuitbreakerCallsFailedTotal(); // Open the circuit again, and reset the half-open result counter circuitBreakerState.setCircuitState(CircuitBreakerState.CircuitState.OPEN); @@ -206,7 +206,7 @@ private Object circuitBreak(InvocationContext context) throws Exception { // If the invocation context hasn't thrown an error, record a success circuitBreakerState.incrementHalfOpenSuccessfulResultCounter(); - getMetrics().incrementCircuitbreakerCallsSucceededTotal(context); + getMetrics().incrementCircuitbreakerCallsSucceededTotal(); logger.log(Level.FINER, "Number of consecutive successful circuitbreaker executions = {0}", circuitBreakerState.getHalfOpenSuccessFulResultCounter()); @@ -278,7 +278,7 @@ private void breakCircuitIfRequired(long failureThreshold, CircuitBreakerState c circuitBreakerState.setCircuitState(CircuitBreakerState.CircuitState.OPEN); // Update the opened metric counter - getMetrics().incrementCircuitbreakerOpenedTotal(context); + getMetrics().incrementCircuitbreakerOpenedTotal(); // Kick off a thread that will half-open the circuit after the specified delay getExecution().scheduleDelayed(delayMillis, circuitBreakerState::halfOpen); diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/FaultToleranceInterceptor.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/FaultToleranceInterceptor.java index 7e94ffd72bd..d6d7f07a78e 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/FaultToleranceInterceptor.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/FaultToleranceInterceptor.java @@ -17,9 +17,9 @@ import org.eclipse.microprofile.faulttolerance.exceptions.FaultToleranceDefinitionException; import org.glassfish.internal.api.Globals; -import fish.payara.microprofile.faulttolerance.FaultToleranceEnvironment; -import fish.payara.microprofile.faulttolerance.cdi.FaultToleranceCdiUtils.Stereotypes; +import fish.payara.microprofile.faulttolerance.FaultToleranceService; import fish.payara.microprofile.faulttolerance.policy.FaultTolerancePolicy; +import fish.payara.microprofile.faulttolerance.service.Stereotypes; @Interceptor @FaultToleranceBehaviour @@ -34,8 +34,8 @@ public class FaultToleranceInterceptor implements Stereotypes, Serializable, Pri @AroundInvoke public Object intercept(InvocationContext context) throws Exception { try { - FaultToleranceEnvironment env = - Globals.getDefaultBaseServiceLocator().getService(FaultToleranceEnvironment.class); + FaultToleranceService env = + Globals.getDefaultBaseServiceLocator().getService(FaultToleranceService.class); FaultTolerancePolicy policy = FaultTolerancePolicy.get(context, () -> env.getConfig(context, this)); if (policy.isPresent) { return policy.proceed(context, env); diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/RetryInterceptor.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/RetryInterceptor.java index 4bb3bb1c699..23862033187 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/RetryInterceptor.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/RetryInterceptor.java @@ -72,9 +72,9 @@ public Object intercept(InvocationContext context) throws Exception { try { // Attempt to proceed the InvocationContext with Asynchronous semantics if Fault Tolerance is enabled for this // method - if (getConfig().isNonFallbackEnabled(context) && getConfig().isEnabled(Retry.class, context)) { + if (getConfig().isNonFallbackEnabled() && getConfig().isEnabled(Retry.class)) { // Increment the invocations metric - getMetrics().incrementInvocationsTotal(context); + getMetrics().incrementInvocationsTotal(); logger.log(Level.FINER, "Proceeding invocation with retry semantics"); resultValue = retry(context); @@ -84,16 +84,16 @@ public Object intercept(InvocationContext context) throws Exception { resultValue = context.proceed(); } } catch (Exception ex) { - Fallback fallback = getConfig().getAnnotation(Fallback.class, context); + Fallback fallback = getConfig().getAnnotation(Fallback.class); // Only fall back if the annotation hasn't been disabled - if (fallback != null && getConfig().isEnabled(Fallback.class, context)) { + if (fallback != null && getConfig().isEnabled(Fallback.class)) { logger.log(Level.FINE, "Fallback annotation found on method - falling back from Retry"); FallbackPolicy fallbackPolicy = new FallbackPolicy(fallback, getConfig(), getExecution(), getMetrics(), context); resultValue = fallbackPolicy.fallback(context, ex); } else { // Increment the failure counter metric - getMetrics().incrementInvocationsFailedTotal(context); + getMetrics().incrementInvocationsFailedTotal(); throw ex; } } @@ -110,27 +110,27 @@ public Object intercept(InvocationContext context) throws Exception { */ private Object retry(InvocationContext context) throws Exception { Object resultValue = null; - Retry retry = getConfig().getAnnotation(Retry.class, context); + Retry retry = getConfig().getAnnotation(Retry.class); try { resultValue = context.proceed(); - getMetrics().incrementRetryCallsSucceededNotRetriedTotal(context); + getMetrics().incrementRetryCallsSucceededNotRetriedTotal(); } catch (Exception ex) { - final Class[] retryOn = getConfig().retryOn(retry, context); - final Class[] abortOn = getConfig().abortOn(retry, context); + final Class[] retryOn = getConfig().retryOn(retry); + final Class[] abortOn = getConfig().abortOn(retry); if (!shouldRetry(retryOn, abortOn, ex)) { logger.log(Level.FINE, "Exception is contained in retryOn or abortOn, not retrying.", ex); throw ex; } - int maxRetries = getConfig().maxRetries(retry, context); - long delay = getConfig().delay(retry, context); - ChronoUnit delayUnit = getConfig().delayUnit(retry, context); - long maxDuration = getConfig().maxDuration(retry, context); - ChronoUnit durationUnit = getConfig().durationUnit(retry, context); - long jitter = getConfig().jitter(retry, context); - ChronoUnit jitterDelayUnit = getConfig().jitterDelayUnit(retry, context); + int maxRetries = getConfig().maxRetries(retry); + long delay = getConfig().delay(retry); + ChronoUnit delayUnit = getConfig().delayUnit(retry); + long maxDuration = getConfig().maxDuration(retry); + ChronoUnit durationUnit = getConfig().durationUnit(retry); + long jitter = getConfig().jitter(retry); + ChronoUnit jitterDelayUnit = getConfig().jitterDelayUnit(retry); long delayMillis = Duration.of(delay, delayUnit).toMillis(); long jitterMillis = Duration.of(jitter, jitterDelayUnit).toMillis(); @@ -147,11 +147,11 @@ private Object retry(InvocationContext context) throws Exception { logger.log(Level.FINER, "Retrying until maxDuration is breached."); while (System.currentTimeMillis() < timeoutTime) { - getMetrics().incrementRetryRetriesTotal(context); + getMetrics().incrementRetryRetriesTotal(); try { resultValue = context.proceed(); succeeded = true; - getMetrics().incrementRetryCallsSucceededRetriedTotal(context); + getMetrics().incrementRetryCallsSucceededRetriedTotal(); break; } catch (Exception caughtException) { retryException = caughtException; @@ -172,10 +172,10 @@ private Object retry(InvocationContext context) throws Exception { } else if (maxRetries == -1 && maxDuration == 0) { logger.log(Level.INFO, "Retrying potentially forever!"); while (true) { - getMetrics().incrementRetryRetriesTotal(context); + getMetrics().incrementRetryRetriesTotal(); try { resultValue = context.proceed(); - getMetrics().incrementRetryCallsSucceededRetriedTotal(context); + getMetrics().incrementRetryCallsSucceededRetriedTotal(); succeeded = true; break; } catch (Exception caughtException) { @@ -199,10 +199,10 @@ private Object retry(InvocationContext context) throws Exception { "Retrying as long as maxDuration ({0}ms) isn''t breached, and no more than {1} times", new Object[]{Duration.of(maxDuration, durationUnit).toMillis(), maxRetries}); while (maxRetries > 0 && System.currentTimeMillis() < timeoutTime) { - getMetrics().incrementRetryRetriesTotal(context); + getMetrics().incrementRetryRetriesTotal(); try { resultValue = context.proceed(); - getMetrics().incrementRetryCallsSucceededRetriedTotal(context); + getMetrics().incrementRetryCallsSucceededRetriedTotal(); succeeded = true; break; } catch (Exception caughtException) { @@ -226,10 +226,10 @@ private Object retry(InvocationContext context) throws Exception { } else { logger.log(Level.INFO, "Retrying no more than {0} times", maxRetries); while (maxRetries > 0) { - getMetrics().incrementRetryRetriesTotal(context); + getMetrics().incrementRetryRetriesTotal(); try { resultValue = context.proceed(); - getMetrics().incrementRetryCallsSucceededRetriedTotal(context); + getMetrics().incrementRetryCallsSucceededRetriedTotal(); succeeded = true; break; } catch (Exception caughtException) { @@ -256,7 +256,7 @@ private Object retry(InvocationContext context) throws Exception { } if (!succeeded) { - getMetrics().incrementRetryCallsFailedTotal(context); + getMetrics().incrementRetryCallsFailedTotal(); throw retryException; } } diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/TimeoutInterceptor.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/TimeoutInterceptor.java index b07a82cf084..f97217ffc60 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/TimeoutInterceptor.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/TimeoutInterceptor.java @@ -76,12 +76,12 @@ public Object intercept(InvocationContext context) throws Exception { try { // Attempt to proceed the InvocationContext with Asynchronous semantics if Fault Tolerance is enabled for this // method - if (getConfig().isNonFallbackEnabled(context) && getConfig().isEnabled(Timeout.class, context)) { + if (getConfig().isNonFallbackEnabled() && getConfig().isEnabled(Timeout.class)) { // Only increment the invocations metric if the Retry, Bulkhead, and CircuitBreaker annotations aren't present - if (getConfig().getAnnotation(Bulkhead.class, context) == null - && getConfig().getAnnotation(Retry.class, context) == null - && getConfig().getAnnotation(CircuitBreaker.class, context) == null) { - getMetrics().incrementInvocationsTotal(context); + if (getConfig().getAnnotation(Bulkhead.class) == null + && getConfig().getAnnotation(Retry.class) == null + && getConfig().getAnnotation(CircuitBreaker.class) == null) { + getMetrics().incrementInvocationsTotal(); } logger.log(Level.FINER, "Proceeding invocation with timeout semantics"); @@ -92,23 +92,23 @@ && getConfig().getAnnotation(CircuitBreaker.class, context) == null) { resultValue = context.proceed(); } } catch (Exception ex) { - Retry retry = getConfig().getAnnotation(Retry.class, context); + Retry retry = getConfig().getAnnotation(Retry.class); if (retry != null) { logger.log(Level.FINE, "Retry annotation found on method, propagating error upwards."); throw ex; } - Fallback fallback = getConfig().getAnnotation(Fallback.class, context); + Fallback fallback = getConfig().getAnnotation(Fallback.class); // Only fall back if the annotation hasn't been disabled - if (fallback != null && getConfig().isEnabled(Fallback.class, context)) { + if (fallback != null && getConfig().isEnabled(Fallback.class)) { logger.log(Level.FINE, "Fallback annotation found on method, and no Retry annotation - " + "falling back from Timeout"); FallbackPolicy fallbackPolicy = new FallbackPolicy(fallback, getConfig(), getExecution(), getMetrics(), context); resultValue = fallbackPolicy.fallback(context, ex); } else { // Increment the failure counter metric - getMetrics().incrementInvocationsFailedTotal(context); + getMetrics().incrementInvocationsFailedTotal(); throw ex; } } @@ -124,10 +124,10 @@ && getConfig().getAnnotation(CircuitBreaker.class, context) == null) { private Object timeout(InvocationContext context) throws Exception { Object resultValue = null; - Timeout timeout = getConfig().getAnnotation(Timeout.class, context); + Timeout timeout = getConfig().getAnnotation(Timeout.class); - long value = getConfig().value(timeout, context); - ChronoUnit unit = getConfig().unit(timeout, context); + long value = getConfig().value(timeout); + ChronoUnit unit = getConfig().unit(timeout); Future timeoutFuture = null; long timeoutMillis = Duration.of(value, unit).toMillis(); @@ -141,33 +141,33 @@ private Object timeout(InvocationContext context) throws Exception { if (System.currentTimeMillis() > timeoutTime) { // Record the timeout for MP Metrics - getMetrics().incrementTimeoutCallsTimedOutTotal(context); + getMetrics().incrementTimeoutCallsTimedOutTotal(); logger.log(Level.FINE, "Execution timed out"); throw new TimeoutException(); } // Record the execution time for MP Metrics - getMetrics().addTimeoutExecutionDuration(System.nanoTime() - executionStartTime, context); + getMetrics().addTimeoutExecutionDuration(System.nanoTime() - executionStartTime); // Record the successfuly completion for MP Metrics - getMetrics().incrementTimeoutCallsNotTimedOutTotal(context); + getMetrics().incrementTimeoutCallsNotTimedOutTotal(); } catch (InterruptedException ie) { // Record the execution time for MP Metrics - getMetrics().addTimeoutExecutionDuration(System.nanoTime() - executionStartTime, context); + getMetrics().addTimeoutExecutionDuration(System.nanoTime() - executionStartTime); if (System.currentTimeMillis() > timeoutTime) { // Record the timeout for MP Metrics - getMetrics().incrementTimeoutCallsTimedOutTotal(context); + getMetrics().incrementTimeoutCallsTimedOutTotal(); logger.log(Level.FINE, "Execution timed out"); throw new TimeoutException(ie); } } catch (Exception ex) { // Record the execution time for MP Metrics - getMetrics().addTimeoutExecutionDuration(System.nanoTime() - executionStartTime, context); + getMetrics().addTimeoutExecutionDuration(System.nanoTime() - executionStartTime); // Deal with cases where someone has caught the thread.interrupt() and thrown the exception as something else if (ex.getCause() instanceof InterruptedException && System.currentTimeMillis() > timeoutTime) { // Record the timeout for MP Metrics - getMetrics().incrementTimeoutCallsTimedOutTotal(context); + getMetrics().incrementTimeoutCallsTimedOutTotal(); logger.log(Level.FINE, "Execution timed out"); throw new TimeoutException(ex); } diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/fallback/FallbackPolicy.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/fallback/FallbackPolicy.java index 658ab1bd37d..5b506ac73f6 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/fallback/FallbackPolicy.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/fallback/FallbackPolicy.java @@ -40,10 +40,10 @@ package fish.payara.microprofile.faulttolerance.interceptors.fallback; import fish.payara.microprofile.faulttolerance.FaultToleranceConfig; -import fish.payara.microprofile.faulttolerance.FaultToleranceEnvironment; +import fish.payara.microprofile.faulttolerance.FaultToleranceService; +import fish.payara.microprofile.faulttolerance.service.FaultToleranceUtils; import fish.payara.microprofile.faulttolerance.FaultToleranceExecutionContext; import fish.payara.microprofile.faulttolerance.FaultToleranceMetrics; -import fish.payara.microprofile.faulttolerance.cdi.FaultToleranceCdiUtils; import java.util.logging.Level; import java.util.logging.Logger; @@ -64,15 +64,15 @@ public class FallbackPolicy { private final Class> fallbackClass; private final String fallbackMethod; - private final FaultToleranceEnvironment execution; + private final FaultToleranceService execution; private final FaultToleranceMetrics metrics; - public FallbackPolicy(Fallback fallback, FaultToleranceConfig config, FaultToleranceEnvironment execution, FaultToleranceMetrics metrics, + public FallbackPolicy(Fallback fallback, FaultToleranceConfig config, FaultToleranceService execution, FaultToleranceMetrics metrics, InvocationContext context) { this.execution = execution; this.metrics = metrics; - this.fallbackClass = config.value(fallback, context); - this.fallbackMethod = config.fallbackMethod(fallback, context); + this.fallbackClass = config.value(fallback); + this.fallbackMethod = config.fallbackMethod(fallback); } /** @@ -88,7 +88,7 @@ public Object fallback(InvocationContext context, Throwable exception) throws Ex if (fallbackMethod != null && !fallbackMethod.isEmpty()) { logger.log(Level.FINE, "Using fallback method: {0}", fallbackMethod); - resultValue = FaultToleranceCdiUtils + resultValue = FaultToleranceUtils .getAnnotatedMethodClass(context, Fallback.class) .getDeclaredMethod(fallbackMethod, context.getMethod().getParameterTypes()) .invoke(context.getTarget(), context.getParameters()); @@ -100,10 +100,10 @@ public Object fallback(InvocationContext context, Throwable exception) throws Ex resultValue = CDI.current().select(fallbackClass).get().handle(executionContext); } - metrics.incrementFallbackCallsTotal(context); + metrics.incrementFallbackCallsTotal(); } catch (Exception ex) { // Increment the failure counter metric - metrics.incrementInvocationsFailedTotal(context); + metrics.incrementInvocationsFailedTotal(); throw ex; } finally { execution.endTrace(); diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/AsynchronousPolicy.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/AsynchronousPolicy.java index 5f94d95462d..9f3ddccf7ab 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/AsynchronousPolicy.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/AsynchronousPolicy.java @@ -13,6 +13,8 @@ /** * The resolved "cached" information of a {@link Asynchronous} annotation an a specific method. + * + * @author Jan Bernitt */ public final class AsynchronousPolicy extends Policy { @@ -24,7 +26,7 @@ private AsynchronousPolicy() { } public static AsynchronousPolicy create(InvocationContext context, FaultToleranceConfig config) { - if (config.isAnnotationPresent(Asynchronous.class, context) && config.isEnabled(Asynchronous.class, context)) { + if (config.isAnnotationPresent(Asynchronous.class) && config.isEnabled(Asynchronous.class)) { checkReturnsFutureOrCompletionStage(context.getMethod()); return context.getMethod().getReturnType() == Future.class ? FUTURE : COMPLETION_STAGE; } diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/BulkheadPolicy.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/BulkheadPolicy.java index 92ff153912e..b9190bb856e 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/BulkheadPolicy.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/BulkheadPolicy.java @@ -10,6 +10,8 @@ /** * The resolved "cached" information of a {@link Bulkhead} annotation an a specific method. + * + * @author Jan Bernitt */ public final class BulkheadPolicy extends Policy { @@ -24,11 +26,11 @@ public BulkheadPolicy(Method annotatedMethod, int value, int waitingTaskQueue) { } public static BulkheadPolicy create(InvocationContext context, FaultToleranceConfig config) { - if (config.isAnnotationPresent(Bulkhead.class, context) && config.isEnabled(Bulkhead.class, context)) { - Bulkhead annotation = config.getAnnotation(Bulkhead.class, context); + if (config.isAnnotationPresent(Bulkhead.class) && config.isEnabled(Bulkhead.class)) { + Bulkhead annotation = config.getAnnotation(Bulkhead.class); return new BulkheadPolicy(context.getMethod(), - config.value(annotation, context), - config.waitingTaskQueue(annotation, context)); + config.value(annotation), + config.waitingTaskQueue(annotation)); } return null; } diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/CircuitBreakerPolicy.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/CircuitBreakerPolicy.java index c05fedd15b8..cb7cd3aa8d3 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/CircuitBreakerPolicy.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/CircuitBreakerPolicy.java @@ -12,6 +12,8 @@ /** * The resolved "cached" information of a {@link CircuitBreaker} annotation an a specific method. + * + * @author Jan Bernitt */ public final class CircuitBreakerPolicy extends Policy { @@ -40,15 +42,15 @@ public CircuitBreakerPolicy(Method annotatedMethod, Class[] } public static CircuitBreakerPolicy create(InvocationContext context, FaultToleranceConfig config) { - if (config.isAnnotationPresent(CircuitBreaker.class, context) && config.isEnabled(CircuitBreaker.class, context)) { - CircuitBreaker annotation = config.getAnnotation(CircuitBreaker.class, context); + if (config.isAnnotationPresent(CircuitBreaker.class) && config.isEnabled(CircuitBreaker.class)) { + CircuitBreaker annotation = config.getAnnotation(CircuitBreaker.class); return new CircuitBreakerPolicy(context.getMethod(), - config.failOn(annotation, context), - config.delay(annotation, context), - config.delayUnit(annotation, context), - config.requestVolumeThreshold(annotation, context), - config.failureRatio(annotation, context), - config.successThreshold(annotation, context)); + config.failOn(annotation), + config.delay(annotation), + config.delayUnit(annotation), + config.requestVolumeThreshold(annotation), + config.failureRatio(annotation), + config.successThreshold(annotation)); } return null; } diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/FallbackPolicy.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/FallbackPolicy.java index f5fcc672ab5..e10b5d6be08 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/FallbackPolicy.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/FallbackPolicy.java @@ -14,6 +14,8 @@ /** * The resolved "cached" information of a {@link Fallback} annotation an a specific method. + * + * @author Jan Bernitt */ public final class FallbackPolicy extends Policy { @@ -42,11 +44,11 @@ public FallbackPolicy(Method annotated, Class> valu } public static FallbackPolicy create(InvocationContext context, FaultToleranceConfig config) { - if (config.isAnnotationPresent(Fallback.class, context) && config.isEnabled(Fallback.class, context)) { - Fallback annotation = config.getAnnotation(Fallback.class, context); + if (config.isAnnotationPresent(Fallback.class) && config.isEnabled(Fallback.class)) { + Fallback annotation = config.getAnnotation(Fallback.class); return new FallbackPolicy(context.getMethod(), - config.value(annotation, context), - config.fallbackMethod(annotation, context)); + config.value(annotation), + config.fallbackMethod(annotation)); } return null; } diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/FaultTolerancePolicy.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/FaultTolerancePolicy.java index aa41d3b4e83..b3a5efec99a 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/FaultTolerancePolicy.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/FaultTolerancePolicy.java @@ -22,7 +22,7 @@ import org.eclipse.microprofile.faulttolerance.exceptions.TimeoutException; import fish.payara.microprofile.faulttolerance.FaultToleranceConfig; -import fish.payara.microprofile.faulttolerance.FaultToleranceEnvironment; +import fish.payara.microprofile.faulttolerance.FaultToleranceService; import fish.payara.microprofile.faulttolerance.FaultToleranceMetrics; import fish.payara.microprofile.faulttolerance.state.BulkheadSemaphore; import fish.payara.microprofile.faulttolerance.state.CircuitBreakerState; @@ -53,8 +53,9 @@ public static void clean() { map -> map.entrySet().removeIf(entry -> now > entry.getValue().expiresMillis)); } - public static FaultTolerancePolicy asAnnotated(Method annotated) { - return create(new StaticAnalysisMethodContext(annotated), () -> FaultToleranceConfig.ANNOTATED); + public static FaultTolerancePolicy asAnnotated(Class target, Method annotated) { + return create(new StaticAnalysisContext(target, annotated), + () -> FaultToleranceConfig.asAnnotated(target, annotated)); } /** @@ -76,8 +77,8 @@ public static FaultTolerancePolicy get(InvocationContext context, Supplier configSpplier) { FaultToleranceConfig config = configSpplier.get(); return new FaultTolerancePolicy( - config.isNonFallbackEnabled(context), - config.isMetricsEnabled(context), + config.isNonFallbackEnabled(), + config.isMetricsEnabled(), AsynchronousPolicy.create(context, config), BulkheadPolicy.create(context, config), CircuitBreakerPolicy.create(context, config), @@ -143,15 +144,15 @@ public boolean isTimeoutPresent() { static final class FaultToleranceInvocation { final InvocationContext context; - final FaultToleranceEnvironment env; + final FaultToleranceService service; final FaultToleranceMetrics metrics; final CompletableFuture asyncResult; final Set asyncWorkers; - FaultToleranceInvocation(InvocationContext context, FaultToleranceEnvironment env, FaultToleranceMetrics metrics, + FaultToleranceInvocation(InvocationContext context, FaultToleranceService service, FaultToleranceMetrics metrics, CompletableFuture asyncResult, Set asyncWorkers) { this.context = context; - this.env = env; + this.service = service; this.metrics = metrics; this.asyncResult = asyncResult; this.asyncWorkers = asyncWorkers; @@ -190,25 +191,25 @@ void timeoutIfConcludedConcurrently() throws TimeoutException { * The call chain goes from 1) down to 6) skipping stages that are not requested by this policy. * * Asynchronous execution branches to new threads in stage 1) and 3) each executed by the - * {@link FaultToleranceEnvironment#runAsynchronous(CompletableFuture, Callable)}. + * {@link FaultToleranceService#runAsynchronous(CompletableFuture, Callable)}. * * @param context intercepted call context - * @param env the environment used to execute the FT behaviour + * @param service the environment used to execute the FT behaviour * @return the result of {@link InvocationContext#proceed()} wrapped with FT behaviour * @throws Exception as thrown by the wrapped invocation or a {@link FaultToleranceException} */ - public Object proceed(InvocationContext context, FaultToleranceEnvironment env) throws Exception { + public Object proceed(InvocationContext context, FaultToleranceService service) throws Exception { if (!isPresent) { return context.proceed(); } FaultToleranceMetrics metrics = isMetricsEnabled - ? env.getMetrics(context) + ? service.getMetrics(context) : FaultToleranceMetrics.DISABLED; try { - metrics.incrementInvocationsTotal(context); - return processAsynchronousStage(context, env, metrics); + metrics.incrementInvocationsTotal(); + return processAsynchronousStage(context, service, metrics); } catch (Exception e) { - metrics.incrementInvocationsFailedTotal(context); + metrics.incrementInvocationsFailedTotal(); throw e; } } @@ -216,10 +217,10 @@ public Object proceed(InvocationContext context, FaultToleranceEnvironment env) /** * Stage that takes care of the {@link AsynchronousPolicy} handling. */ - private Object processAsynchronousStage(InvocationContext context, FaultToleranceEnvironment env, + private Object processAsynchronousStage(InvocationContext context, FaultToleranceService service, FaultToleranceMetrics metrics) throws Exception { if (!isAsynchronous()) { - return processFallbackStage(new FaultToleranceInvocation(context, env, metrics, null, null)); + return processFallbackStage(new FaultToleranceInvocation(context, service, metrics, null, null)); } Set workers = ConcurrentHashMap.newKeySet(); CompletableFuture asyncResult = new CompletableFuture() { @@ -242,16 +243,16 @@ public boolean cancel(boolean mayInterruptIfRunning) { @Override public boolean completeExceptionally(Throwable ex) { if (ex instanceof ExecutionException) { - metrics.incrementInvocationsFailedTotal(context); + metrics.incrementInvocationsFailedTotal(); return super.completeExceptionally(ex.getCause()); } else if (ex instanceof FaultToleranceException || !asynchronous.isSuccessWhenCompletedExceptionally()) { - metrics.incrementInvocationsFailedTotal(context); + metrics.incrementInvocationsFailedTotal(); } return super.completeExceptionally(ex); } }; - FaultToleranceInvocation invocation = new FaultToleranceInvocation(context, env, metrics, asyncResult, workers); - env.runAsynchronous(asyncResult, + FaultToleranceInvocation invocation = new FaultToleranceInvocation(context, service, metrics, asyncResult, workers); + service.runAsynchronous(asyncResult, () -> invocation.runStageWithWorker(() -> processFallbackStage(invocation))); return asyncResult; } @@ -266,11 +267,11 @@ private Object processFallbackStage(FaultToleranceInvocation invocation) throws try { return processRetryStage(invocation); } catch (Exception ex) { - invocation.metrics.incrementFallbackCallsTotal(invocation.context); + invocation.metrics.incrementFallbackCallsTotal(); if (fallback.isHandlerPresent()) { - return invocation.env.fallbackHandle(fallback.value, invocation.context, ex); + return invocation.service.fallbackHandle(fallback.value, invocation.context, ex); } - return invocation.env.fallbackInvoke(fallback.method, invocation.context); + return invocation.service.fallbackInvoke(fallback.method, invocation.context); } } @@ -281,32 +282,31 @@ private Object processRetryStage(FaultToleranceInvocation invocation) throws Exc int totalAttempts = retry.totalAttempts(); int attemptsLeft = totalAttempts; Long retryTimeoutTime = retry.timeoutTimeNow(); - InvocationContext context = invocation.context; while (attemptsLeft > 0) { attemptsLeft--; try { boolean firstAttempt = attemptsLeft == totalAttempts - 1; if (!firstAttempt) { - invocation.metrics.incrementRetryRetriesTotal(context); + invocation.metrics.incrementRetryRetriesTotal(); } Object resultValue = isAsynchronous() ? processRetryAsync(invocation) : processCircuitBreakerStage(invocation, null); if (firstAttempt) { - invocation.metrics.incrementRetryCallsSucceededNotRetriedTotal(context); + invocation.metrics.incrementRetryCallsSucceededNotRetriedTotal(); } else { - invocation.metrics.incrementRetryCallsSucceededRetriedTotal(context); + invocation.metrics.incrementRetryCallsSucceededRetriedTotal(); } return resultValue; } catch (Exception ex) { if (attemptsLeft <= 0 || !retry.retryOn(ex) || retryTimeoutTime != null && System.currentTimeMillis() >= retryTimeoutTime) { - invocation.metrics.incrementRetryCallsFailedTotal(context); + invocation.metrics.incrementRetryCallsFailedTotal(); throw ex; } if (retry.isDelayed()) { - invocation.env.delay(retry.jitteredDelay(), context); + invocation.service.delay(retry.jitteredDelay(), invocation.context); } } } @@ -316,7 +316,7 @@ private Object processRetryStage(FaultToleranceInvocation invocation) throws Exc private Object processRetryAsync(FaultToleranceInvocation invocation) throws Exception { CompletableFuture asyncAttempt = new CompletableFuture<>(); - invocation.env.runAsynchronous(asyncAttempt, + invocation.service.runAsynchronous(asyncAttempt, () -> invocation.runStageWithWorker(() -> processCircuitBreakerStage(invocation, asyncAttempt))); try { asyncAttempt.get(); // wait and only proceed on success @@ -341,33 +341,32 @@ private Object processCircuitBreakerStage(FaultToleranceInvocation invocation, C if (!isCircuitBreakerPresent()) { return processTimeoutStage(invocation, asyncAttempt); } - InvocationContext context = invocation.context; - CircuitBreakerState state = invocation.env.getState(circuitBreaker.requestVolumeThreshold, context); + CircuitBreakerState state = invocation.service.getState(circuitBreaker.requestVolumeThreshold, invocation.context); if (isMetricsEnabled) { - invocation.metrics.insertCircuitbreakerOpenTotal(state::nanosOpen, context); - invocation.metrics.insertCircuitbreakerHalfOpenTotal(state::nanosHalfOpen, context); - invocation.metrics.insertCircuitbreakerClosedTotal(state::nanosClosed, context); + invocation.metrics.insertCircuitbreakerOpenTotal(state::nanosOpen); + invocation.metrics.insertCircuitbreakerHalfOpenTotal(state::nanosHalfOpen); + invocation.metrics.insertCircuitbreakerClosedTotal(state::nanosClosed); } Object resultValue = null; switch (state.getCircuitState()) { default: case OPEN: - invocation.metrics.incrementCircuitbreakerCallsPreventedTotal(context); + invocation.metrics.incrementCircuitbreakerCallsPreventedTotal(); throw new CircuitBreakerOpenException(); case HALF_OPEN: try { resultValue = processTimeoutStage(invocation, asyncAttempt); } catch (Exception ex) { - invocation.metrics.incrementCircuitbreakerCallsFailedTotal(context); + invocation.metrics.incrementCircuitbreakerCallsFailedTotal(); if (circuitBreaker.failOn(ex)) { - invocation.metrics.incrementCircuitbreakerOpenedTotal(context); + invocation.metrics.incrementCircuitbreakerOpenedTotal(); state.open(); - invocation.env.scheduleDelayed(circuitBreaker.delay, state::halfOpen); + invocation.service.scheduleDelayed(circuitBreaker.delay, state::halfOpen); } throw ex; } state.halfOpenSuccessful(circuitBreaker.successThreshold); - invocation.metrics.incrementCircuitbreakerCallsSucceededTotal(context); + invocation.metrics.incrementCircuitbreakerCallsSucceededTotal(); return resultValue; case CLOSED: Exception failedOn = null; @@ -375,21 +374,21 @@ private Object processCircuitBreakerStage(FaultToleranceInvocation invocation, C resultValue = processTimeoutStage(invocation, asyncAttempt); state.recordClosedResult(true); } catch (Exception ex) { - invocation.metrics.incrementCircuitbreakerCallsFailedTotal(context); + invocation.metrics.incrementCircuitbreakerCallsFailedTotal(); if (circuitBreaker.failOn(ex)) { state.recordClosedResult(false); } failedOn = ex; } if (state.isOverFailureThreshold(circuitBreaker.requestVolumeThreshold, circuitBreaker.failureRatio)) { - invocation.metrics.incrementCircuitbreakerOpenedTotal(context); + invocation.metrics.incrementCircuitbreakerOpenedTotal(); state.open(); - invocation.env.scheduleDelayed(circuitBreaker.delay, state::halfOpen); + invocation.service.scheduleDelayed(circuitBreaker.delay, state::halfOpen); } if (failedOn != null) { throw failedOn; } - invocation.metrics.incrementCircuitbreakerCallsSucceededTotal(context); + invocation.metrics.incrementCircuitbreakerCallsSucceededTotal(); return resultValue; } } @@ -405,11 +404,10 @@ private Object processTimeoutStage(FaultToleranceInvocation invocation, Completa long timeoutTime = System.currentTimeMillis() + timeoutDuration; Thread current = Thread.currentThread(); AtomicBoolean didTimeout = new AtomicBoolean(false); - InvocationContext context = invocation.context; - Future timeout = invocation.env.scheduleDelayed(timeoutDuration, () -> { + Future timeout = invocation.service.scheduleDelayed(timeoutDuration, () -> { didTimeout.set(true); current.interrupt(); - invocation.metrics.incrementTimeoutCallsTimedOutTotal(context); + invocation.metrics.incrementTimeoutCallsTimedOutTotal(); if (asyncAttempt != null) { // we do this since interrupting not necessarily returns directly or ever but the attempt should timeout now asyncAttempt.completeExceptionally(new TimeoutException()); @@ -424,7 +422,7 @@ private Object processTimeoutStage(FaultToleranceInvocation invocation, Completa if (didTimeout.get() || System.currentTimeMillis() > timeoutTime) { throw new TimeoutException(); } - invocation.metrics.incrementTimeoutCallsNotTimedOutTotal(context); + invocation.metrics.incrementTimeoutCallsNotTimedOutTotal(); return resultValue; } catch (Exception ex) { if ((ex instanceof InterruptedException || ex.getCause() instanceof InterruptedException) @@ -433,7 +431,7 @@ private Object processTimeoutStage(FaultToleranceInvocation invocation, Completa } throw ex; } finally { - invocation.metrics.addTimeoutExecutionDuration(System.nanoTime() - executionStartTime, context); + invocation.metrics.addTimeoutExecutionDuration(System.nanoTime() - executionStartTime); timeout.cancel(true); } } @@ -446,35 +444,35 @@ private Object processBulkheadStage(FaultToleranceInvocation invocation) throws return proceed(invocation); } InvocationContext context = invocation.context; - BulkheadSemaphore concurrentExecutions = invocation.env.getConcurrentExecutions(bulkhead.value, context); + BulkheadSemaphore concurrentExecutions = invocation.service.getConcurrentExecutions(bulkhead.value, context); BulkheadSemaphore waitingQueuePopulation = !isAsynchronous() ? null - : invocation.env.getWaitingQueuePopulation(bulkhead.waitingTaskQueue, context); + : invocation.service.getWaitingQueuePopulation(bulkhead.waitingTaskQueue, context); if (isMetricsEnabled) { - invocation.metrics.insertBulkheadConcurrentExecutions(concurrentExecutions::acquiredPermits, context); + invocation.metrics.insertBulkheadConcurrentExecutions(concurrentExecutions::acquiredPermits); if (waitingQueuePopulation != null) { - invocation.metrics.insertBulkheadWaitingQueuePopulation(waitingQueuePopulation::acquiredPermits, context); + invocation.metrics.insertBulkheadWaitingQueuePopulation(waitingQueuePopulation::acquiredPermits); } } long executionStartTime = System.nanoTime(); if (concurrentExecutions.tryAcquire(0, TimeUnit.SECONDS)) { - invocation.metrics.incrementBulkheadCallsAcceptedTotal(context); + invocation.metrics.incrementBulkheadCallsAcceptedTotal(); if (isAsynchronous()) { - invocation.metrics.addBulkheadWaitingDuration(0L, context); // we did not wait but need to factor in the invocation for histogram quartiles + invocation.metrics.addBulkheadWaitingDuration(0L); // we did not wait but need to factor in the invocation for histogram quartiles } try { return proceed(invocation); } finally { - invocation.metrics.addBulkheadExecutionDuration(System.nanoTime() - executionStartTime, context); + invocation.metrics.addBulkheadExecutionDuration(System.nanoTime() - executionStartTime); concurrentExecutions.release(); } } if (waitingQueuePopulation == null) { // plain semaphore style, fail: - invocation.metrics.incrementBulkheadCallsRejectedTotal(context); + invocation.metrics.incrementBulkheadCallsRejectedTotal(); throw new BulkheadException("No free work permits."); } // from here: queueing style: if (waitingQueuePopulation.tryAcquire(0, TimeUnit.SECONDS)) { - invocation.metrics.incrementBulkheadCallsAcceptedTotal(context); + invocation.metrics.incrementBulkheadCallsAcceptedTotal(); long queueStartTime = System.nanoTime(); try { concurrentExecutions.acquire(); // block until execution permit becomes available @@ -482,7 +480,7 @@ private Object processBulkheadStage(FaultToleranceInvocation invocation) throws waitingQueuePopulation.release(); throw new BulkheadException(ex); } finally { - invocation.metrics.addBulkheadWaitingDuration(System.nanoTime() - queueStartTime, context); + invocation.metrics.addBulkheadWaitingDuration(System.nanoTime() - queueStartTime); } waitingQueuePopulation.release(); try { @@ -491,7 +489,7 @@ private Object processBulkheadStage(FaultToleranceInvocation invocation) throws concurrentExecutions.release(); } } - invocation.metrics.incrementBulkheadCallsRejectedTotal(context); + invocation.metrics.incrementBulkheadCallsRejectedTotal(); throw new BulkheadException("No free work or queue permits."); } diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/Policy.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/Policy.java index 51c72bd74d1..3d1a3d1002f 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/Policy.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/Policy.java @@ -7,6 +7,11 @@ import org.eclipse.microprofile.faulttolerance.exceptions.FaultToleranceDefinitionException; +/** + * Contains general helper method for FT policy validation. + * + * @author Jan Bernitt + */ public abstract class Policy implements Serializable { public static void checkAtLeast(long minimum, Method annotatedMethod, Class annotationType, diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/RetryPolicy.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/RetryPolicy.java index 9456e042144..508b904e6e2 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/RetryPolicy.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/RetryPolicy.java @@ -13,6 +13,8 @@ /** * The resolved "cached" information of a {@link Retry} annotation an a specific method. + * + * @author Jan Bernitt */ public final class RetryPolicy extends Policy { @@ -52,18 +54,18 @@ public RetryPolicy(Method annotatedMethod, int maxRetries, long delay, ChronoUni } public static RetryPolicy create(InvocationContext context, FaultToleranceConfig config) { - if (config.isAnnotationPresent(Retry.class, context) && config.isEnabled(Retry.class, context)) { - Retry annotation = config.getAnnotation(Retry.class, context); + if (config.isAnnotationPresent(Retry.class) && config.isEnabled(Retry.class)) { + Retry annotation = config.getAnnotation(Retry.class); return new RetryPolicy(context.getMethod(), - config.maxRetries(annotation, context), - config.delay(annotation, context), - config.delayUnit(annotation, context), - config.maxDuration(annotation, context), - config.durationUnit(annotation, context), - config.jitter(annotation, context), - config.jitterDelayUnit(annotation, context), - config.retryOn(annotation, context), - config.abortOn(annotation, context)); + config.maxRetries(annotation), + config.delay(annotation), + config.delayUnit(annotation), + config.maxDuration(annotation), + config.durationUnit(annotation), + config.jitter(annotation), + config.jitterDelayUnit(annotation), + config.retryOn(annotation), + config.abortOn(annotation)); } return NONE; } diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/StaticAnalysisMethodContext.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/StaticAnalysisContext.java similarity index 63% rename from appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/StaticAnalysisMethodContext.java rename to appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/StaticAnalysisContext.java index 88bada99734..e37cfdce1c3 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/StaticAnalysisMethodContext.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/StaticAnalysisContext.java @@ -6,17 +6,30 @@ import javax.interceptor.InvocationContext; -final class StaticAnalysisMethodContext implements InvocationContext { +final class StaticAnalysisContext implements InvocationContext { + private final Class targetClass; private final Method annotated; + private transient Object target; - public StaticAnalysisMethodContext(Method annotated) { + public StaticAnalysisContext(Class targetClass, Method annotated) { + this.targetClass = targetClass; this.annotated = annotated; } @Override public Object getTarget() { - throw new UnsupportedOperationException(); + if (target == null) { + try { + target = targetClass.newInstance(); + } catch (Exception e) { + target = new UnsupportedOperationException(); + } + } + if (target instanceof UnsupportedOperationException) { + throw (UnsupportedOperationException) target; + } + return target; } @Override diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/TimeoutPolicy.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/TimeoutPolicy.java index 713ab4133c4..0d772b64218 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/TimeoutPolicy.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/TimeoutPolicy.java @@ -12,6 +12,8 @@ /** * The resolved "cached" information of a {@link Timeout} annotation an a specific method. + * + * @author Jan Bernitt */ public final class TimeoutPolicy extends Policy { @@ -25,11 +27,11 @@ public TimeoutPolicy(Method annotatedMethod, long value, ChronoUnit unit) { } public static TimeoutPolicy create(InvocationContext context, FaultToleranceConfig config) { - if (config.isAnnotationPresent(Timeout.class, context) && config.isEnabled(Timeout.class, context)) { - Timeout annotation = config.getAnnotation(Timeout.class, context); + if (config.isAnnotationPresent(Timeout.class) && config.isEnabled(Timeout.class)) { + Timeout annotation = config.getAnnotation(Timeout.class); return new TimeoutPolicy(context.getMethod(), - config.value(annotation, context), - config.unit(annotation, context)); + config.value(annotation), + config.unit(annotation)); } return null; } diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/FaultToleranceApplicationState.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/service/FaultToleranceApplicationState.java similarity index 89% rename from appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/FaultToleranceApplicationState.java rename to appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/service/FaultToleranceApplicationState.java index 0a0e72552a4..22018a4f378 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/FaultToleranceApplicationState.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/service/FaultToleranceApplicationState.java @@ -37,7 +37,7 @@ * only if the new code is made subject to such option by the copyright * holder. */ -package fish.payara.microprofile.faulttolerance; +package fish.payara.microprofile.faulttolerance.service; import fish.payara.microprofile.faulttolerance.state.BulkheadSemaphore; import fish.payara.microprofile.faulttolerance.state.CircuitBreakerState; @@ -46,7 +46,6 @@ import java.util.concurrent.atomic.AtomicReference; /** - * * @author Andrew Pielage andrew.pielage@payara.fish */ public class FaultToleranceApplicationState { @@ -54,8 +53,8 @@ public class FaultToleranceApplicationState { private final Map> circuitBreakerStates = new ConcurrentHashMap<>(); private final Map> bulkheadExecutionSemaphores = new ConcurrentHashMap<>(); private final Map> bulkheadExecutionQueueSemaphores = new ConcurrentHashMap<>(); - private final AtomicReference config = new AtomicReference<>(); - private final AtomicReference metrics = new AtomicReference<>(); + private final AtomicReference config = new AtomicReference<>(); + private final AtomicReference metrics = new AtomicReference<>(); public Map> getCircuitBreakerStates() { return circuitBreakerStates; @@ -69,11 +68,11 @@ public Map> getBulkheadExecutionQueueSema return bulkheadExecutionQueueSemaphores; } - public AtomicReference getConfig() { + public AtomicReference getConfig() { return config; } - public AtomicReference getMetrics() { + public AtomicReference getMetrics() { return metrics; } } diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/service/FaultToleranceConfigFactory.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/service/FaultToleranceConfigFactory.java new file mode 100644 index 00000000000..62aaabc20b9 --- /dev/null +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/service/FaultToleranceConfigFactory.java @@ -0,0 +1,317 @@ +package fish.payara.microprofile.faulttolerance.service; + +import java.lang.annotation.Annotation; +import java.time.temporal.ChronoUnit; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.interceptor.InvocationContext; + +import org.eclipse.microprofile.config.Config; +import org.eclipse.microprofile.config.ConfigProvider; +import org.eclipse.microprofile.faulttolerance.Bulkhead; +import org.eclipse.microprofile.faulttolerance.CircuitBreaker; +import org.eclipse.microprofile.faulttolerance.Fallback; +import org.eclipse.microprofile.faulttolerance.FallbackHandler; +import org.eclipse.microprofile.faulttolerance.Retry; +import org.eclipse.microprofile.faulttolerance.Timeout; + +import fish.payara.microprofile.faulttolerance.FaultToleranceConfig; + +/** + * A {@link FaultToleranceConfig} using {@link Config} to resolve overrides. + * The {@link Config} is resolved using the {@link ConfigProvider} if needed. + * + * @author Jan Bernitt + */ +final class FaultToleranceConfigFactory implements FaultToleranceConfig { + + private static final String NON_FALLBACK_ENABLED_PROPERTY = "MP_Fault_Tolerance_NonFallback_Enabled"; + private static final String METRICS_ENABLED_PROPERTY = "MP_Fault_Tolerance_Metrics_Enabled"; + private static final String INTERCEPTOR_PRIORITY_PROPERTY = "mp.fault.tolerance.interceptor.priority"; + + private static final Logger logger = Logger.getLogger(FaultToleranceConfigFactory.class.getName()); + + /** + * These tree properties should only be read once at the start of the application, therefore they are cached in + * static field. + */ + private final AtomicReference nonFallbackEnabled; + private final AtomicReference metricsEnabled; + private final AtomicInteger interceptorPriority; + private final Config config; + private final Stereotypes sterotypes; + private final InvocationContext context; + + public FaultToleranceConfigFactory(Stereotypes sterotypes) { + this.sterotypes = sterotypes; + this.config = resolveConfig(); + this.nonFallbackEnabled = new AtomicReference<>(); + this.metricsEnabled = new AtomicReference<>(); + this.interceptorPriority = new AtomicInteger(-1); + this.context = null; // factory is unbound + } + + private FaultToleranceConfigFactory(InvocationContext context, Stereotypes sterotypes, Config config, + AtomicReference nonFallbackEnabled, AtomicReference metricsEnabled, + AtomicInteger interceptorPriority) { + this.context = context; + this.sterotypes = sterotypes; + this.config = config; + this.nonFallbackEnabled = nonFallbackEnabled; + this.metricsEnabled = metricsEnabled; + this.interceptorPriority = interceptorPriority; + } + + private static Config resolveConfig() { + logger.log(Level.INFO, "Resolving Fault Tolerance Config from Provider."); + try { + return ConfigProvider.getConfig(); + } catch (Exception ex) { + logger.log(Level.INFO, "No Config could be found, using annotation values only.", ex); + return null; + } + } + + /* + * Factory method + */ + + public FaultToleranceConfig bindTo(InvocationContext context) { + return config == null + ? FaultToleranceConfig.asAnnotated(context.getTarget().getClass(), context.getMethod()) + : new FaultToleranceConfigFactory(context, sterotypes, config, nonFallbackEnabled, metricsEnabled, + interceptorPriority); + } + + /* + * General + */ + + @Override + public A getAnnotation(Class annotationType) { + return FaultToleranceUtils.getAnnotation(sterotypes, annotationType, context); + } + + @Override + public boolean isNonFallbackEnabled() { + if (nonFallbackEnabled.get() == null) { + nonFallbackEnabled.compareAndSet(null, + config.getOptionalValue(NON_FALLBACK_ENABLED_PROPERTY, Boolean.class).orElse(true)); + } + return nonFallbackEnabled.get().booleanValue(); + } + + @Override + public boolean isMetricsEnabled() { + if (metricsEnabled.get() == null) { + metricsEnabled.compareAndSet(null, + config.getOptionalValue(METRICS_ENABLED_PROPERTY, Boolean.class).orElse(true)); + } + return metricsEnabled.get().booleanValue(); + } + + @Override + public boolean isEnabled(Class annotationType) { + return FaultToleranceUtils.getEnabledOverrideValue(config, annotationType, context, + annotationType == Fallback.class || isNonFallbackEnabled()); + } + + @Override + public int interceptorPriority() { + return interceptorPriority.updateAndGet(priority -> priority > 0 ? priority + : config.getOptionalValue(INTERCEPTOR_PRIORITY_PROPERTY, Integer.class) + .orElse(DEFAULT_INTERCEPTOR_PRIORITY)); + } + + /* + * Retry + */ + + @Override + public int maxRetries(Retry annotation) { + return intValue(Retry.class, "maxRetries", annotation.maxRetries()); + } + + @Override + public long delay(Retry annotation) { + return longValue(Retry.class, "delay", annotation.delay()); + } + + @Override + public ChronoUnit delayUnit(Retry annotation) { + return chronoUnitValue(Retry.class, "delayUnit", annotation.delayUnit()); + } + + @Override + public long maxDuration(Retry annotation) { + return longValue(Retry.class, "maxDuration", annotation.maxDuration()); + } + + @Override + public ChronoUnit durationUnit(Retry annotation) { + return chronoUnitValue(Retry.class, "durationUnit", annotation.durationUnit()); + } + + @Override + public long jitter(Retry annotation) { + return longValue(Retry.class, "jitter", annotation.jitter()); + } + + @Override + public ChronoUnit jitterDelayUnit(Retry annotation) { + return chronoUnitValue(Retry.class, "jitterDelayUnit", annotation.jitterDelayUnit()); + } + + @Override + public Class[] retryOn(Retry annotation) { + return getClassArrayValue(Retry.class, "retryOn", annotation.retryOn()); + } + + @Override + public Class[] abortOn(Retry annotation) { + return getClassArrayValue(Retry.class, "abortOn", annotation.abortOn()); + } + + + /* + * Circuit-Breaker + */ + + @Override + public Class[] failOn(CircuitBreaker annotation) { + return getClassArrayValue(CircuitBreaker.class, "failOn", annotation.failOn()); + } + + @Override + public long delay(CircuitBreaker annotation) { + return longValue(CircuitBreaker.class, "delay", annotation.delay()); + } + + @Override + public ChronoUnit delayUnit(CircuitBreaker annotation) { + return chronoUnitValue(CircuitBreaker.class, "delayUnit", annotation.delayUnit()); + } + + @Override + public int requestVolumeThreshold(CircuitBreaker annotation) { + return intValue(CircuitBreaker.class, "requestVolumeThreshold", annotation.requestVolumeThreshold()); + } + + @Override + public double failureRatio(CircuitBreaker annotation) { + return value(CircuitBreaker.class, "failureRatio", Double.class, annotation.failureRatio()); + } + + @Override + public int successThreshold(CircuitBreaker annotation) { + return intValue(CircuitBreaker.class, "successThreshold", annotation.successThreshold()); + } + + + /* + * Bulkhead + */ + + @Override + public int value(Bulkhead annotation) { + return intValue(Bulkhead.class, "value", annotation.value()); + } + + @Override + public int waitingTaskQueue(Bulkhead annotation) { + return intValue(Bulkhead.class, "waitingTaskQueue", annotation.waitingTaskQueue()); + } + + + /* + * Timeout + */ + + @Override + public long value(Timeout annotation) { + return longValue(Timeout.class, "value", annotation.value()); + } + + @Override + public ChronoUnit unit(Timeout annotation) { + return chronoUnitValue(Timeout.class, "unit", annotation.unit()); + } + + + /* + * Fallback + */ + + @SuppressWarnings("unchecked") + @Override + public Class> value(Fallback annotation) { + String className = FaultToleranceUtils.getOverrideValue(config, Fallback.class, "value", + context, String.class, null); + if (className == null) { + return annotation.value(); + } + try { + return (Class>) Thread.currentThread().getContextClassLoader() + .loadClass(className); + } catch (ClassNotFoundException e) { + return annotation.value(); + } + } + + @Override + public String fallbackMethod(Fallback annotation) { + return value(Fallback.class, "fallbackMethod", String.class, annotation.fallbackMethod()); + } + + + /* + * Helpers + */ + + private long longValue(Class annotationType, String attribute, + long annotationValue) { + return value(annotationType, attribute, Long.class, annotationValue); + } + + private int intValue(Class annotationType, String attribute, + int annotationValue) { + return value(annotationType, attribute, Integer.class, annotationValue); + } + + private ChronoUnit chronoUnitValue(Class annotationType, String attribute, + ChronoUnit annotationValue) { + return value(annotationType, attribute, ChronoUnit.class, annotationValue); + } + + private T value(Class annotationType, String attribute, Class valueType, + T annotationValue) { + return FaultToleranceUtils.getOverrideValue(config, annotationType, attribute, context, valueType, annotationValue); + } + + private Class[] getClassArrayValue(Class annotationType, + String attributeName, Class[] annotationValue) { + String classNames = FaultToleranceUtils.getOverrideValue(config, annotationType, attributeName, context, + String.class, null); + if (classNames == null) { + return annotationValue; + } + try { + List> classList = new ArrayList<>(); + // Remove any curly or square brackets from the string, as well as any spaces and ".class"es + for (String className : classNames.replaceAll("[\\{\\[ \\]\\}]", "").replaceAll("\\.class", "") + .split(",")) { + classList.add(Class.forName(className)); + } + return classList.toArray(annotationValue); + } catch (ClassNotFoundException cnfe) { + logger.log(Level.INFO, "Could not find class from " + attributeName + " config, defaulting to annotation. " + + "Make sure you give the full canonical class name.", cnfe); + return annotationValue; + } + } +} diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/service/FaultToleranceMetricsFactory.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/service/FaultToleranceMetricsFactory.java new file mode 100644 index 00000000000..49332bd25da --- /dev/null +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/service/FaultToleranceMetricsFactory.java @@ -0,0 +1,94 @@ +package fish.payara.microprofile.faulttolerance.service; + +import java.util.function.LongSupplier; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.enterprise.inject.spi.CDI; +import javax.interceptor.InvocationContext; + +import org.eclipse.microprofile.metrics.Gauge; +import org.eclipse.microprofile.metrics.MetricRegistry; + +import fish.payara.microprofile.faulttolerance.FaultToleranceMetrics; + +/** + * The {@link FaultToleranceMetricsFactory} works both as a factory where {@link #bindTo(InvocationContext)} is used to + * create context aware instances of a {@link FaultToleranceMetrics} and as the bound {@link FaultToleranceMetrics}. For + * thread safety immutable objects are used or created. + * + * This {@link FaultToleranceMetrics} uses {@link CDI} to resolve the {@link MetricRegistry}. When resolution fails + * {@link #bindTo(InvocationContext)} will use {@link FaultToleranceMetrics#DISABLED}. + * + * @author Jan Bernitt + */ +final class FaultToleranceMetricsFactory implements FaultToleranceMetrics { + + private static final Logger logger = Logger.getLogger(FaultToleranceMetricsFactory.class.getName()); + + private final MetricRegistry metricRegistry; + /** + * This is "cached" as soon as an instance is bound using the + * {@link #FaultToleranceMetricsFactory(MetricRegistry, String)} constructor. + */ + private final String canonicalMethodName; + + public FaultToleranceMetricsFactory() { + this.metricRegistry = resolveRegistry(); + this.canonicalMethodName = "(unbound)"; + } + + private FaultToleranceMetricsFactory(MetricRegistry metricRegistry, String canonicalMethodName) { + this.metricRegistry = metricRegistry; + this.canonicalMethodName = canonicalMethodName; + } + + private static MetricRegistry resolveRegistry() { + logger.log(Level.INFO, "Resolving Fault Tolerance MetricRegistry from CDI."); + try { + return CDI.current().select(MetricRegistry.class).get(); + } catch (Exception ex) { + logger.log(Level.INFO, "No MetricRegistry could be found, disabling metrics.", ex); + return null; + } + } + + /* + * Factory method + */ + + FaultToleranceMetrics bindTo(InvocationContext context) { + return metricRegistry == null + ? FaultToleranceMetrics.DISABLED + : new FaultToleranceMetricsFactory(metricRegistry, FaultToleranceUtils.getCanonicalMethodName(context)); + } + + /* + * General Metrics + */ + + @Override + public void increment(String keyPattern) { + metricRegistry.counter(metricName(keyPattern)).inc(); + } + + @Override + public void add(String keyPattern, long duration) { + metricRegistry.histogram(metricName(keyPattern)).update(duration); + } + + @Override + public void insert(String keyPattern, LongSupplier gauge) { + String metricName = metricName(keyPattern); + Gauge existingGauge = metricRegistry.getGauges().get(metricName); + if (existingGauge == null) { + Gauge newGauge = gauge::getAsLong; + metricRegistry.register(metricName, newGauge); + } + } + + private String metricName(String keyPattern) { + return String.format(keyPattern, canonicalMethodName); + } + +} diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/service/FaultToleranceServiceImpl.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/service/FaultToleranceServiceImpl.java new file mode 100644 index 00000000000..9efead6c993 --- /dev/null +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/service/FaultToleranceServiceImpl.java @@ -0,0 +1,371 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright (c) 2017-2018 Payara Foundation and/or its affiliates. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common Development + * and Distribution License("CDDL") (collectively, the "License"). You + * may not use this file except in compliance with the License. You can + * obtain a copy of the License at + * https://github.com/payara/Payara/blob/master/LICENSE.txt + * See the License for the specific + * language governing permissions and limitations under the License. + * + * When distributing the software, include this License Header Notice in each + * file and include the License file at glassfish/legal/LICENSE.txt. + * + * GPL Classpath Exception: + * The Payara Foundation designates this particular file as subject to the "Classpath" + * exception as provided by the Payara Foundation in the GPL Version 2 section of the License + * file that accompanied this code. + * + * Modifications: + * If applicable, add the following below the License Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyright [year] [name of copyright owner]" + * + * Contributor(s): + * If you wish your version of this file to be governed by only the CDDL or + * only the GPL Version 2, indicate your decision by adding "[Contributor] + * elects to include this software in this distribution under the [CDDL or GPL + * Version 2] license." If you don't indicate a single choice of license, a + * recipient has the option to distribute your version of this file under + * either the CDDL, the GPL Version 2 or to extend the choice of license to + * its licensees as provided above. However, if you add GPL Version 2 code + * and therefore, elected the GPL Version 2 license, then the option applies + * only if the new code is made subject to such option by the copyright + * holder. + */ +package fish.payara.microprofile.faulttolerance.service; + +import fish.payara.microprofile.faulttolerance.FaultToleranceConfig; +import fish.payara.microprofile.faulttolerance.FaultToleranceExecutionContext; +import fish.payara.microprofile.faulttolerance.FaultToleranceMetrics; +import fish.payara.microprofile.faulttolerance.FaultToleranceService; +import fish.payara.microprofile.faulttolerance.FaultToleranceServiceConfiguration; +import fish.payara.microprofile.faulttolerance.policy.AsynchronousPolicy; +import fish.payara.microprofile.faulttolerance.state.BulkheadSemaphore; +import fish.payara.microprofile.faulttolerance.state.CircuitBreakerState; +import fish.payara.notification.requesttracing.RequestTraceSpan; +import fish.payara.nucleus.requesttracing.RequestTracingService; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.Map; +import java.util.concurrent.Callable; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.annotation.PostConstruct; +import javax.enterprise.concurrent.ManagedExecutorService; +import javax.enterprise.concurrent.ManagedScheduledExecutorService; +import javax.enterprise.inject.spi.CDI; +import javax.inject.Inject; +import javax.inject.Named; +import javax.interceptor.InvocationContext; +import javax.naming.InitialContext; +import javax.naming.NamingException; + +import org.eclipse.microprofile.faulttolerance.FallbackHandler; +import org.eclipse.microprofile.faulttolerance.exceptions.FaultToleranceDefinitionException; +import org.glassfish.api.StartupRunLevel; +import org.glassfish.api.admin.ServerEnvironment; +import org.glassfish.api.event.EventListener; +import org.glassfish.api.event.Events; +import org.glassfish.api.invocation.ComponentInvocation; +import org.glassfish.api.invocation.InvocationManager; +import org.glassfish.hk2.api.ServiceLocator; +import org.glassfish.hk2.runlevel.RunLevel; +import org.glassfish.internal.data.ApplicationInfo; +import org.glassfish.internal.data.ApplicationRegistry; +import org.glassfish.internal.deployment.Deployment; +import org.jvnet.hk2.annotations.ContractsProvided; +import org.jvnet.hk2.annotations.Optional; +import org.jvnet.hk2.annotations.Service; + +/** + * Base Service for MicroProfile Fault Tolerance. + * + * @author Andrew Pielage + * @author Jan Bernitt (2.0) + */ +@ContractsProvided(FaultToleranceService.class) +@Service(name = "microprofile-fault-tolerance-service") +@RunLevel(StartupRunLevel.VAL) +public class FaultToleranceServiceImpl implements EventListener, FaultToleranceService { + + private static final Logger logger = Logger.getLogger(FaultToleranceServiceImpl.class.getName()); + + @Inject + @Named(ServerEnvironment.DEFAULT_INSTANCE_NAME) + @Optional + private FaultToleranceServiceConfiguration serviceConfig; + + private InvocationManager invocationManager; + + @Inject + private RequestTracingService requestTracingService; + + @Inject + private ServiceLocator serviceLocator; + + @Inject + private Events events; + + private final Map stateByApplication = new ConcurrentHashMap<>(); + private ManagedScheduledExecutorService defaultScheduledExecutorService; + private ManagedExecutorService defaultExecutorService; + + @PostConstruct + public void postConstruct() throws NamingException { + events.register(this); + serviceConfig = serviceLocator.getService(FaultToleranceServiceConfiguration.class); + invocationManager = serviceLocator.getService(InvocationManager.class); + requestTracingService = serviceLocator.getService(RequestTracingService.class); + InitialContext context = new InitialContext(); + defaultExecutorService = (ManagedExecutorService) context.lookup("java:comp/DefaultManagedExecutorService"); + defaultScheduledExecutorService = (ManagedScheduledExecutorService) context + .lookup("java:comp/DefaultManagedScheduledExecutorService"); + } + + @Override + public void event(Event event) { + if (event.is(Deployment.APPLICATION_UNLOADED)) { + ApplicationInfo info = (ApplicationInfo) event.hook(); + deregisterApplication(info.getName()); + } + } + + @Override + public FaultToleranceConfig getConfig(InvocationContext context, Stereotypes stereotypes) { + FaultToleranceApplicationState appState = getApplicationState(getApplicationContext(context)); + return appState.getConfig().updateAndGet( + config -> config != null ? config : new FaultToleranceConfigFactory(stereotypes)).bindTo(context); + } + + @Override + public FaultToleranceMetrics getMetrics(InvocationContext context) { + FaultToleranceApplicationState appState = getApplicationState(getApplicationContext(context)); + return appState.getMetrics().updateAndGet( + metrics -> metrics != null ? metrics : new FaultToleranceMetricsFactory()).bindTo(context); + } + + //TODO use the scheduler to schedule a clean of FT Info + + private ManagedExecutorService getManagedExecutorService() { + return lookup(serviceConfig.getManagedExecutorService(), defaultExecutorService); + } + + private ManagedScheduledExecutorService getManagedScheduledExecutorService() { + return lookup(serviceConfig.getManagedScheduledExecutorService(), defaultScheduledExecutorService); + } + + @SuppressWarnings("unchecked") + private static T lookup(String name, T defaultInstance) { + // If no name has been set, just get the default + if (name == null || name.isEmpty()) { + return defaultInstance; + } + try { + return (T) new InitialContext().lookup(name); + } catch (Exception ex) { + logger.log(Level.INFO, "Could not find configured , " + name + ", so resorting to default", ex); + return defaultInstance; + } + } + + private FaultToleranceApplicationState getApplicationState(String applicationName) { + return stateByApplication.computeIfAbsent(applicationName, key -> new FaultToleranceApplicationState()); + } + + private BulkheadSemaphore getBulkheadExecutionSemaphore(String applicationName, Object invocationTarget, + Method annotatedMethod, int bulkheadValue) { + return getApplicationState(applicationName).getBulkheadExecutionSemaphores() + .computeIfAbsent(invocationTarget, key -> new ConcurrentHashMap<>()) + .computeIfAbsent( getFullMethodSignature(annotatedMethod), key -> new BulkheadSemaphore(bulkheadValue)); + } + + private BulkheadSemaphore getBulkheadExecutionQueueSemaphore(String applicationName, Object invocationTarget, + Method annotatedMethod, int bulkheadWaitingTaskQueue) { + return getApplicationState(applicationName).getBulkheadExecutionQueueSemaphores() + .computeIfAbsent(invocationTarget, key -> new ConcurrentHashMap<>()) + .computeIfAbsent( getFullMethodSignature(annotatedMethod), key -> new BulkheadSemaphore(bulkheadWaitingTaskQueue)); + } + + private CircuitBreakerState getCircuitBreakerState(String applicationName, Object invocationTarget, + Method annotatedMethod, int requestVolumeThreshold) { + return getApplicationState(applicationName).getCircuitBreakerStates() + .computeIfAbsent(invocationTarget, key -> new ConcurrentHashMap<>()) + .computeIfAbsent(getFullMethodSignature(annotatedMethod), key -> new CircuitBreakerState(requestVolumeThreshold)); + } + + /** + * Removes an application from the enabled map, CircuitBreaker map, and bulkhead maps + * @param applicationName The name of the application to remove + */ + private void deregisterApplication(String applicationName) { + stateByApplication.remove(applicationName); + } + + /** + * Gets the application name from the invocation manager. Failing that, it will use the module name, component name, + * or method signature (in that order). + * @param invocationManager The invocation manager to get the application name from + * @param context The context of the current invocation + * @return The application name + */ + private String getApplicationContext(InvocationContext context) { + ComponentInvocation currentInvocation = invocationManager.getCurrentInvocation(); + String appName = currentInvocation.getAppName(); + if (appName != null) { + return appName; + } + appName = currentInvocation.getModuleName(); + if (appName != null) { + return appName; + } + appName = currentInvocation.getComponentId(); + // If we've found a component name, check if there's an application registered with the same name + if (appName != null) { + // If it's not directly in the registry, it's possible due to how the componentId is constructed + if (serviceLocator.getService(ApplicationRegistry.class).get(appName) == null) { + // The application name should be the first component + return appName.split("_/")[0]; + } + } + // If we still don't have a name - just construct it from the method signature + return getFullMethodSignature(context.getMethod()); + } + + /** + * Helper method to generate a full method signature consisting of canonical class name, method name, + * parameter types, and return type. + * @param annotatedMethod The annotated Method to generate the signature for + * @return A String in the format of CanonicalClassName#MethodName({ParameterTypes})>ReturnType + */ + private static String getFullMethodSignature(Method annotatedMethod) { + return annotatedMethod.getDeclaringClass().getCanonicalName() + + "#" + annotatedMethod.getName() + + "(" + Arrays.toString(annotatedMethod.getParameterTypes()) + ")" + + ">" + annotatedMethod.getReturnType().getSimpleName(); + } + + private void startFaultToleranceSpan(RequestTraceSpan span, InvocationContext invocationContext) { + if (requestTracingService != null && requestTracingService.isRequestTracingEnabled()) { + addGenericFaultToleranceRequestTracingDetails(span, invocationContext); + requestTracingService.startTrace(span); + } + } + + private void endFaultToleranceSpan() { + if (requestTracingService != null && requestTracingService.isRequestTracingEnabled()) { + requestTracingService.endTrace(); + } + } + + private void addGenericFaultToleranceRequestTracingDetails(RequestTraceSpan span, + InvocationContext invocationContext) { + span.addSpanTag("App Name", invocationManager.getCurrentInvocation().getAppName()); + span.addSpanTag("Component ID", invocationManager.getCurrentInvocation().getComponentId()); + span.addSpanTag("Module Name", invocationManager.getCurrentInvocation().getModuleName()); + span.addSpanTag("Class Name", invocationContext.getMethod().getDeclaringClass().getName()); + span.addSpanTag("Method Name", invocationContext.getMethod().getName()); + } + + + /* + * Execution + */ + + @Override + public CircuitBreakerState getState(int requestVolumeThreshold, InvocationContext context) { + return getCircuitBreakerState(getApplicationContext(context), context.getTarget(), + context.getMethod(), requestVolumeThreshold); + } + + @Override + public BulkheadSemaphore getConcurrentExecutions(int maxConcurrentThreads, InvocationContext context) { + return getBulkheadExecutionSemaphore(getApplicationContext(context), + context.getTarget(), context.getMethod(), maxConcurrentThreads); + } + + @Override + public BulkheadSemaphore getWaitingQueuePopulation(int queueCapacity, InvocationContext context) { + return getBulkheadExecutionQueueSemaphore(getApplicationContext(context), + context.getTarget(), context.getMethod(), queueCapacity); + } + + @Override + public void delay(long delayMillis, InvocationContext context) throws InterruptedException { + if (delayMillis <= 0) { + return; + } + startTrace("delayRetry", context); + try { + Thread.sleep(delayMillis); + } finally { + endTrace(); + } + } + + @Override + public void runAsynchronous(CompletableFuture asyncResult, Callable operation) throws Exception { + Runnable task = () -> { + if (!asyncResult.isCancelled() && !Thread.currentThread().isInterrupted()) { + try { + Future futureResult = AsynchronousPolicy.toFuture(operation.call()); + if (!asyncResult.isCancelled()) { // could be cancelled in the meanwhile + if (!asyncResult.isDone()) { + asyncResult.complete(futureResult.get()); + } + } else { + futureResult.cancel(true); + } + } catch (Exception ex) { + // Note that even ExecutionException is not unpacked (intentionally) + asyncResult.completeExceptionally(ex); + } + } + }; + getManagedExecutorService().submit(task); + } + + @Override + public Future scheduleDelayed(long delayMillis, Runnable operation) throws Exception { + return getManagedScheduledExecutorService().schedule(operation, delayMillis, TimeUnit.MILLISECONDS); + } + + @Override + public Object fallbackHandle(Class> fallbackClass, InvocationContext context, + Exception exception) throws Exception { + return CDI.current().select(fallbackClass).get() + .handle(new FaultToleranceExecutionContext(context.getMethod(), context.getParameters(), exception)); + } + + @Override + public Object fallbackInvoke(Method fallbackMethod, InvocationContext context) throws Exception { + try { + fallbackMethod.setAccessible(true); + return fallbackMethod.invoke(context.getTarget(), context.getParameters()); + } catch (InvocationTargetException e) { + throw (Exception) e.getTargetException(); + } catch (IllegalAccessException e) { + throw new FaultToleranceDefinitionException(e); // should not happen as we validated + } + } + + @Override + public void startTrace(String method, InvocationContext context) { + startFaultToleranceSpan(new RequestTraceSpan(method), context); + } + + @Override + public void endTrace() { + endFaultToleranceSpan(); + } +} diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/cdi/FaultToleranceCdiUtils.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/service/FaultToleranceUtils.java similarity index 92% rename from appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/cdi/FaultToleranceCdiUtils.java rename to appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/service/FaultToleranceUtils.java index 2fbc9653223..5118358f1d6 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/cdi/FaultToleranceCdiUtils.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/service/FaultToleranceUtils.java @@ -37,30 +37,32 @@ * only if the new code is made subject to such option by the copyright * holder. */ -package fish.payara.microprofile.faulttolerance.cdi; +package fish.payara.microprofile.faulttolerance.service; import java.lang.annotation.Annotation; import java.util.Optional; -import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; + +import javax.enterprise.inject.spi.Annotated; import javax.interceptor.InvocationContext; import org.eclipse.microprofile.config.Config; +import org.eclipse.microprofile.faulttolerance.Asynchronous; +import org.eclipse.microprofile.faulttolerance.Bulkhead; +import org.eclipse.microprofile.faulttolerance.CircuitBreaker; +import org.eclipse.microprofile.faulttolerance.Fallback; +import org.eclipse.microprofile.faulttolerance.Retry; +import org.eclipse.microprofile.faulttolerance.Timeout; /** * Utility Methods for the Fault Tolerance Interceptors. + * * @author Andrew Pielage + * @author Jan Bernitt (2.0) */ -public class FaultToleranceCdiUtils { - - private static final Logger logger = Logger.getLogger(FaultToleranceCdiUtils.class.getName()); - - public interface Stereotypes { +public class FaultToleranceUtils { - boolean isStereotype(Class annotationType); - - Set getStereotypeDefinition(Class stereotype); - } + private static final Logger logger = Logger.getLogger(FaultToleranceUtils.class.getName()); /** * Gets the annotation from the method that triggered the interceptor. @@ -255,4 +257,13 @@ public static String getPlainCanonicalName(Class type) public static String getCanonicalMethodName(InvocationContext context) { return getPlainCanonicalName(context.getMethod().getDeclaringClass()) + "." + context.getMethod().getName(); } + + public static boolean isAnnotaetdWithFaultToleranceAnnotations(Annotated element) { + return element.isAnnotationPresent(Asynchronous.class) + || element.isAnnotationPresent(Bulkhead.class) + || element.isAnnotationPresent(CircuitBreaker.class) + || element.isAnnotationPresent(Fallback.class) + || element.isAnnotationPresent(Retry.class) + || element.isAnnotationPresent(Timeout.class); + } } diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/service/Stereotypes.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/service/Stereotypes.java new file mode 100644 index 00000000000..cce146dd841 --- /dev/null +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/service/Stereotypes.java @@ -0,0 +1,16 @@ +package fish.payara.microprofile.faulttolerance.service; + +import java.lang.annotation.Annotation; +import java.util.Set; + +/** + * Decouples sterotype-annotation lookup from application server context. + * + * @author Jan Bernitt + */ +public interface Stereotypes { + + boolean isStereotype(Class annotationType); + + Set getStereotypeDefinition(Class stereotype); +} \ No newline at end of file diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/validators/BulkheadValidator.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/validators/BulkheadValidator.java index 14f52c30fe2..499f429d24a 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/validators/BulkheadValidator.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/validators/BulkheadValidator.java @@ -39,12 +39,13 @@ */ package fish.payara.microprofile.faulttolerance.validators; -import fish.payara.microprofile.faulttolerance.cdi.FaultToleranceCdiUtils; import javax.enterprise.inject.spi.AnnotatedMethod; import org.eclipse.microprofile.config.Config; import org.eclipse.microprofile.faulttolerance.Bulkhead; import org.eclipse.microprofile.faulttolerance.exceptions.FaultToleranceDefinitionException; +import fish.payara.microprofile.faulttolerance.service.FaultToleranceUtils; + /** * Validator for the Fault Tolerance Bulkhead annotation. * @author Andrew Pielage @@ -58,11 +59,11 @@ public class BulkheadValidator { * @param config The config to get any override values from */ public static void validateAnnotation(Bulkhead bulkhead, AnnotatedMethod annotatedMethod, Config config) { - int value = FaultToleranceCdiUtils.getOverrideValue( + int value = FaultToleranceUtils.getOverrideValue( config, Bulkhead.class, "value", annotatedMethod.getJavaMember().getName(), annotatedMethod.getJavaMember().getDeclaringClass().getCanonicalName(), Integer.class) .orElse(bulkhead.value()); - int waitingTaskQueue = FaultToleranceCdiUtils.getOverrideValue( + int waitingTaskQueue = FaultToleranceUtils.getOverrideValue( config, Bulkhead.class, "waitingTaskQueue", annotatedMethod.getJavaMember().getName(), annotatedMethod.getJavaMember().getDeclaringClass().getCanonicalName(), Integer.class) .orElse(bulkhead.waitingTaskQueue()); diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/validators/CircuitBreakerValidator.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/validators/CircuitBreakerValidator.java index 9cf40b23215..52e4f815a3c 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/validators/CircuitBreakerValidator.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/validators/CircuitBreakerValidator.java @@ -39,12 +39,13 @@ */ package fish.payara.microprofile.faulttolerance.validators; -import fish.payara.microprofile.faulttolerance.cdi.FaultToleranceCdiUtils; import javax.enterprise.inject.spi.AnnotatedMethod; import org.eclipse.microprofile.config.Config; import org.eclipse.microprofile.faulttolerance.CircuitBreaker; import org.eclipse.microprofile.faulttolerance.exceptions.FaultToleranceDefinitionException; +import fish.payara.microprofile.faulttolerance.service.FaultToleranceUtils; + /** * Validator for the Fault Tolerance CircuitBreaker annotation. * @author Andrew Pielage @@ -59,22 +60,22 @@ public class CircuitBreakerValidator { */ public static void validateAnnotation(CircuitBreaker circuitBreaker, AnnotatedMethod annotatedMethod, Config config) { - long delay = FaultToleranceCdiUtils.getOverrideValue( + long delay = FaultToleranceUtils.getOverrideValue( config, CircuitBreaker.class, "delay", annotatedMethod.getJavaMember().getName(), annotatedMethod.getJavaMember().getDeclaringClass().getCanonicalName(), Long.class) .orElse(circuitBreaker.delay()); - int requestVolumeThreshold = FaultToleranceCdiUtils.getOverrideValue( + int requestVolumeThreshold = FaultToleranceUtils.getOverrideValue( config, CircuitBreaker.class, "requestVolumeThreshold", annotatedMethod.getJavaMember().getName(), annotatedMethod.getJavaMember().getDeclaringClass().getCanonicalName(), Integer.class) .orElse(circuitBreaker.requestVolumeThreshold()); - double failureRatio = FaultToleranceCdiUtils.getOverrideValue( + double failureRatio = FaultToleranceUtils.getOverrideValue( config, CircuitBreaker.class, "failureRatio", annotatedMethod.getJavaMember().getName(), annotatedMethod.getJavaMember().getDeclaringClass().getCanonicalName(), Double.class) .orElse(circuitBreaker.failureRatio()); - int successThreshold = FaultToleranceCdiUtils.getOverrideValue( + int successThreshold = FaultToleranceUtils.getOverrideValue( config, CircuitBreaker.class, "successThreshold", annotatedMethod.getJavaMember().getName(), annotatedMethod.getJavaMember().getDeclaringClass().getCanonicalName(), Integer.class) .orElse(circuitBreaker.successThreshold()); diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/validators/FallbackValidator.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/validators/FallbackValidator.java index a355d9e19f5..93f442e1a76 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/validators/FallbackValidator.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/validators/FallbackValidator.java @@ -41,7 +41,6 @@ import java.util.Optional; -import fish.payara.microprofile.faulttolerance.cdi.FaultToleranceCdiUtils; import javax.enterprise.inject.spi.AnnotatedMethod; import org.eclipse.microprofile.config.Config; import org.eclipse.microprofile.faulttolerance.ExecutionContext; @@ -49,6 +48,8 @@ import org.eclipse.microprofile.faulttolerance.FallbackHandler; import org.eclipse.microprofile.faulttolerance.exceptions.FaultToleranceDefinitionException; +import fish.payara.microprofile.faulttolerance.service.FaultToleranceUtils; + /** * Validator for the Fault Tolerance Fallback Annotation. * @author Andrew Pielage @@ -66,13 +67,13 @@ public class FallbackValidator { public static void validateAnnotation(Fallback fallback, AnnotatedMethod annotatedMethod, Config config) throws ClassNotFoundException, NoSuchMethodException { // Get the fallbackMethod - String fallbackMethod = FaultToleranceCdiUtils.getOverrideValue( + String fallbackMethod = FaultToleranceUtils.getOverrideValue( config, Fallback.class, "fallbackMethod", annotatedMethod.getJavaMember().getName(), annotatedMethod.getJavaMember().getDeclaringClass().getCanonicalName(), String.class) .orElse(fallback.fallbackMethod()); // Get the fallbackClass, and check that it can be found - Optional fallbackClassName = FaultToleranceCdiUtils + Optional fallbackClassName = FaultToleranceUtils .getOverrideValue(config, Fallback.class, "value", annotatedMethod.getJavaMember().getName(), annotatedMethod.getJavaMember().getDeclaringClass().getCanonicalName(), String.class); @SuppressWarnings("unchecked") diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/validators/RetryValidator.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/validators/RetryValidator.java index bda65c74105..da7cc1380ba 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/validators/RetryValidator.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/validators/RetryValidator.java @@ -39,12 +39,13 @@ */ package fish.payara.microprofile.faulttolerance.validators; -import fish.payara.microprofile.faulttolerance.cdi.FaultToleranceCdiUtils; import javax.enterprise.inject.spi.AnnotatedMethod; import org.eclipse.microprofile.config.Config; import org.eclipse.microprofile.faulttolerance.Retry; import org.eclipse.microprofile.faulttolerance.exceptions.FaultToleranceDefinitionException; +import fish.payara.microprofile.faulttolerance.service.FaultToleranceUtils; + /** * Validator for the Fault Tolerance Retry annotation. * @author Andrew Pielage @@ -58,22 +59,22 @@ public class RetryValidator { * @param config The config to get any override values from */ public static void validateAnnotation(Retry retry, AnnotatedMethod annotatedMethod, Config config) { - int maxRetries = FaultToleranceCdiUtils.getOverrideValue( + int maxRetries = FaultToleranceUtils.getOverrideValue( config, Retry.class, "maxRetries", annotatedMethod.getJavaMember().getName(), annotatedMethod.getJavaMember().getDeclaringClass().getCanonicalName(), Integer.class) .orElse(retry.maxRetries()); - long delay = FaultToleranceCdiUtils.getOverrideValue( + long delay = FaultToleranceUtils.getOverrideValue( config, Retry.class, "delay", annotatedMethod.getJavaMember().getName(), annotatedMethod.getJavaMember().getDeclaringClass().getCanonicalName(), Long.class) .orElse(retry.delay()); - long maxDuration = FaultToleranceCdiUtils.getOverrideValue( + long maxDuration = FaultToleranceUtils.getOverrideValue( config, Retry.class, "maxDuration", annotatedMethod.getJavaMember().getName(), annotatedMethod.getJavaMember().getDeclaringClass().getCanonicalName(), Long.class) .orElse(retry.maxDuration()); - long jitter = FaultToleranceCdiUtils.getOverrideValue( + long jitter = FaultToleranceUtils.getOverrideValue( config, Retry.class, "jitter", annotatedMethod.getJavaMember().getName(), annotatedMethod.getJavaMember().getDeclaringClass().getCanonicalName(), Long.class) .orElse(retry.jitter()); diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/validators/TimeoutValidator.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/validators/TimeoutValidator.java index be554f1265c..187e2f4934a 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/validators/TimeoutValidator.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/validators/TimeoutValidator.java @@ -39,19 +39,20 @@ */ package fish.payara.microprofile.faulttolerance.validators; -import fish.payara.microprofile.faulttolerance.cdi.FaultToleranceCdiUtils; import javax.enterprise.inject.spi.AnnotatedMethod; import org.eclipse.microprofile.config.Config; import org.eclipse.microprofile.faulttolerance.Timeout; import org.eclipse.microprofile.faulttolerance.exceptions.FaultToleranceDefinitionException; +import fish.payara.microprofile.faulttolerance.service.FaultToleranceUtils; + /** * Validator for the Fault Tolerance Timeout annotation. * @author Andrew Pielage */ public class TimeoutValidator { public static void validateAnnotation(Timeout timeout, AnnotatedMethod annotatedMethod, Config config) { - long value = FaultToleranceCdiUtils.getOverrideValue( + long value = FaultToleranceUtils.getOverrideValue( config, Timeout.class, "value", annotatedMethod.getJavaMember().getName(), annotatedMethod.getJavaMember().getDeclaringClass().getCanonicalName(), Long.class) .orElse(timeout.value()); diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/test/TestUtils.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/test/TestUtils.java index 6eecc87245e..084df3d1474 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/test/TestUtils.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/test/TestUtils.java @@ -38,7 +38,8 @@ public static Method getMethod(Class target, String name) { public static void assertAnnotationInvalid(String expectedErrorMessage) { try { Method annotatedMethod = getAnnotatedMethod(); - FaultTolerancePolicy policy = FaultTolerancePolicy.asAnnotated(annotatedMethod); + FaultTolerancePolicy policy = FaultTolerancePolicy.asAnnotated(annotatedMethod.getDeclaringClass(), + annotatedMethod); fail("Annotation should be invalid for " + annotatedMethod + " but got: " + policy); } catch (FaultToleranceDefinitionException ex) { String message = ex.getMessage(); From e4c62441e62ed9787c393c73e6bb3e9e79644616 Mon Sep 17 00:00:00 2001 From: Jan Bernitt Date: Thu, 18 Apr 2019 15:08:23 +0200 Subject: [PATCH 11/30] PAYARA-3468 added tracing and logging, fixed bulkhead metric rejected calls --- .../faulttolerance/FaultToleranceService.java | 2 +- .../cdi/FaultToleranceCDIExtension.java | 2 +- .../interceptors/BulkheadInterceptor.java | 2 +- .../CircuitBreakerInterceptor.java | 4 +- .../interceptors/RetryInterceptor.java | 10 +- .../interceptors/fallback/FallbackPolicy.java | 2 +- .../policy/FaultTolerancePolicy.java | 92 +++++++++++++++---- .../policy/StaticAnalysisContext.java | 5 + .../service/FaultToleranceServiceImpl.java | 4 +- .../state/BulkheadSemaphore.java | 13 +++ .../state/CircuitBreakerState.java | 7 +- .../faulttolerance/test/TestUtils.java | 3 +- 12 files changed, 112 insertions(+), 34 deletions(-) diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/FaultToleranceService.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/FaultToleranceService.java index c058eec1289..075efc52354 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/FaultToleranceService.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/FaultToleranceService.java @@ -42,7 +42,7 @@ public interface FaultToleranceService { Object fallbackInvoke(Method fallbackMethod, InvocationContext context) throws Exception; - void startTrace(String method, InvocationContext context); + void trace(String method, InvocationContext context); void endTrace(); diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/cdi/FaultToleranceCDIExtension.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/cdi/FaultToleranceCDIExtension.java index e84b3bf30eb..2605e8df138 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/cdi/FaultToleranceCDIExtension.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/cdi/FaultToleranceCDIExtension.java @@ -66,7 +66,7 @@ * CDI Extension that does the setup for FT interceptor handling. * * @author Andrew Pielage - * @author Jan Bernitt + * @author Jan Bernitt (2.0) */ public class FaultToleranceCDIExtension implements Extension { diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/BulkheadInterceptor.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/BulkheadInterceptor.java index 918501797f1..c3deb986e15 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/BulkheadInterceptor.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/BulkheadInterceptor.java @@ -160,7 +160,7 @@ private Object bulkhead(InvocationContext context) throws Exception { // If there is a free queue permit, queue for an executor permit try { logger.log(Level.FINER, "Attempting to acquire bulkhead execution semaphore."); - getExecution().startTrace("obtainBulkheadSemaphore", context); + getExecution().trace("obtainBulkheadSemaphore", context); try { bulkheadExecutionSemaphore.acquire(); } finally { diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/CircuitBreakerInterceptor.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/CircuitBreakerInterceptor.java index e2464f96922..10c232119be 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/CircuitBreakerInterceptor.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/CircuitBreakerInterceptor.java @@ -158,7 +158,7 @@ private Object circuitBreak(InvocationContext context) throws Exception { logger.log(Level.FINE, "Caught exception is included in CircuitBreaker failOn, " + "recording failure against CircuitBreaker"); // Add a failure result to the queue - circuitBreakerState.recordClosedResult(Boolean.FALSE); + circuitBreakerState.recordClosedOutcome(Boolean.FALSE); getMetrics().incrementCircuitbreakerCallsFailedTotal(); // Calculate the failure ratio, and if we're over it, open the circuit @@ -172,7 +172,7 @@ private Object circuitBreak(InvocationContext context) throws Exception { } // If everything is bon, add a success value - circuitBreakerState.recordClosedResult(Boolean.TRUE); + circuitBreakerState.recordClosedOutcome(Boolean.TRUE); getMetrics().incrementCircuitbreakerCallsSucceededTotal(); // Calculate the failure ratio, and if we're over it, open the circuit diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/RetryInterceptor.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/RetryInterceptor.java index 23862033187..d21f7891ec7 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/RetryInterceptor.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/RetryInterceptor.java @@ -138,7 +138,7 @@ private Object retry(InvocationContext context) throws Exception { Exception retryException = ex; - getExecution().startTrace("retryMethod", context); + getExecution().trace("retryMethod", context); boolean succeeded = false; @@ -160,7 +160,7 @@ private Object retry(InvocationContext context) throws Exception { } if (delayMillis > 0 || jitterMillis > 0) { - getExecution().startTrace("delayRetry", context); + getExecution().trace("delayRetry", context); try { Thread.sleep(delayMillis + ThreadLocalRandom.current().nextLong(0, jitterMillis)); } finally { @@ -185,7 +185,7 @@ private Object retry(InvocationContext context) throws Exception { } if (delayMillis > 0 || jitterMillis > 0) { - getExecution().startTrace("delayRetry", context); + getExecution().trace("delayRetry", context); try { Thread.sleep(delayMillis + ThreadLocalRandom.current().nextLong(0, jitterMillis)); } finally { @@ -212,7 +212,7 @@ private Object retry(InvocationContext context) throws Exception { } if (delayMillis > 0 || jitterMillis > 0) { - getExecution().startTrace("delayRetry", context); + getExecution().trace("delayRetry", context); try { Thread.sleep(delayMillis + ThreadLocalRandom.current().nextLong(0, jitterMillis)); } finally { @@ -239,7 +239,7 @@ private Object retry(InvocationContext context) throws Exception { } if (delayMillis > 0 || jitterMillis > 0) { - getExecution().startTrace("delayRetry", context); + getExecution().trace("delayRetry", context); try { Thread.sleep(delayMillis + ThreadLocalRandom.current().nextLong(0, jitterMillis)); } finally { diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/fallback/FallbackPolicy.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/fallback/FallbackPolicy.java index 5b506ac73f6..0846bda2558 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/fallback/FallbackPolicy.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/fallback/FallbackPolicy.java @@ -83,7 +83,7 @@ public FallbackPolicy(Fallback fallback, FaultToleranceConfig config, FaultToler */ public Object fallback(InvocationContext context, Throwable exception) throws Exception { Object resultValue = null; - execution.startTrace("executeFallbackMethod", context); + execution.trace("executeFallbackMethod", context); try { if (fallbackMethod != null && !fallbackMethod.isEmpty()) { logger.log(Level.FINE, "Using fallback method: {0}", fallbackMethod); diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/FaultTolerancePolicy.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/FaultTolerancePolicy.java index b3a5efec99a..265f5fad5ac 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/FaultTolerancePolicy.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/FaultTolerancePolicy.java @@ -9,9 +9,10 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Supplier; +import java.util.logging.Level; +import java.util.logging.Logger; import javax.interceptor.InvocationContext; @@ -37,16 +38,25 @@ * effective values. * * The policy class also reduces the need to analyse FT annotations for each invocation and works as a consistent source - * of truth throughout the execution of FT behaviour that is convenient to pass around as a single immutable value. + * of truth throughout the processing of FT behaviour that is convenient to pass around as a single immutable value. * * @author Jan Bernitt */ public final class FaultTolerancePolicy implements Serializable { + static final Logger logger = Logger.getLogger(FaultTolerancePolicy.class.getName()); + private static final long TTL = 60 * 1000; - private static final ConcurrentHashMap, ConcurrentHashMap> POLICY_BY_METHOD = new ConcurrentHashMap<>(); + /** + * A simple cache with a fix {@link #TTL} with a policy for each target method. + */ + private static final ConcurrentHashMap, ConcurrentHashMap> POLICY_BY_METHOD + = new ConcurrentHashMap<>(); + /** + * Removes all expired policies from the cache. + */ public static void clean() { long now = System.currentTimeMillis(); POLICY_BY_METHOD.forEachValue(Long.MAX_VALUE, @@ -174,6 +184,14 @@ void timeoutIfConcludedConcurrently() throws TimeoutException { throw new TimeoutException("Computation already concluded in a concurrent attempt"); } } + + void trace(String method) { + service.trace(method, context); + } + + void endTrace() { + service.endTrace(); + } } /** @@ -195,11 +213,12 @@ void timeoutIfConcludedConcurrently() throws TimeoutException { * * @param context intercepted call context * @param service the environment used to execute the FT behaviour - * @return the result of {@link InvocationContext#proceed()} wrapped with FT behaviour + * @return the result of {@link InvocationContext#proceed()} after applying FT behaviour * @throws Exception as thrown by the wrapped invocation or a {@link FaultToleranceException} */ public Object proceed(InvocationContext context, FaultToleranceService service) throws Exception { if (!isPresent) { + logger.log(Level.FINER, "Fault Tolerance not enabled, proceeding normally."); return context.proceed(); } FaultToleranceMetrics metrics = isMetricsEnabled @@ -222,12 +241,14 @@ private Object processAsynchronousStage(InvocationContext context, FaultToleranc if (!isAsynchronous()) { return processFallbackStage(new FaultToleranceInvocation(context, service, metrics, null, null)); } + logger.log(Level.FINER, "Proceeding invocation with asynchronous semantics"); Set workers = ConcurrentHashMap.newKeySet(); CompletableFuture asyncResult = new CompletableFuture() { @Override public boolean cancel(boolean mayInterruptIfRunning) { if (super.cancel(mayInterruptIfRunning)) { + logger.log(Level.FINE, "Asynchronous computation was cancelled by caller."); if (mayInterruptIfRunning) { workers.forEach(worker -> worker.interrupt()); } @@ -242,6 +263,7 @@ public boolean cancel(boolean mayInterruptIfRunning) { */ @Override public boolean completeExceptionally(Throwable ex) { + logger.log(Level.FINE, "Asynchronous computation completed with exception", ex); if (ex instanceof ExecutionException) { metrics.incrementInvocationsFailedTotal(); return super.completeExceptionally(ex.getCause()); @@ -264,14 +286,20 @@ private Object processFallbackStage(FaultToleranceInvocation invocation) throws if (!isFallbackPresent()) { return processRetryStage(invocation); } + logger.log(Level.FINER, "Proceeding invocation with fallback semantics"); + invocation.trace("executeFallbackMethod"); try { return processRetryStage(invocation); } catch (Exception ex) { invocation.metrics.incrementFallbackCallsTotal(); if (fallback.isHandlerPresent()) { + logger.log(Level.FINE, "Using fallback class: {0}", fallback.value.getName()); return invocation.service.fallbackHandle(fallback.value, invocation.context, ex); } + logger.log(Level.FINE, "Using fallback method: {0}", fallback.method.getName()); return invocation.service.fallbackInvoke(fallback.method, invocation.context); + } finally { + invocation.endTrace(); } } @@ -279,6 +307,9 @@ private Object processFallbackStage(FaultToleranceInvocation invocation) throws * Stage that takes care of the {@link RetryPolicy} handling. */ private Object processRetryStage(FaultToleranceInvocation invocation) throws Exception { + if (!retry.isNone()) { + logger.log(Level.FINER, "Proceeding invocation with retry semantics"); + } int totalAttempts = retry.totalAttempts(); int attemptsLeft = totalAttempts; Long retryTimeoutTime = retry.timeoutTimeNow(); @@ -287,6 +318,7 @@ private Object processRetryStage(FaultToleranceInvocation invocation) throws Exc try { boolean firstAttempt = attemptsLeft == totalAttempts - 1; if (!firstAttempt) { + logger.log(Level.FINER, "Attempting retry."); invocation.metrics.incrementRetryRetriesTotal(); } Object resultValue = isAsynchronous() @@ -299,12 +331,13 @@ private Object processRetryStage(FaultToleranceInvocation invocation) throws Exc } return resultValue; } catch (Exception ex) { - if (attemptsLeft <= 0 - || !retry.retryOn(ex) - || retryTimeoutTime != null && System.currentTimeMillis() >= retryTimeoutTime) { + boolean timedOut = retryTimeoutTime != null && System.currentTimeMillis() >= retryTimeoutTime; + if (attemptsLeft <= 0 || !retry.retryOn(ex) || timedOut) { + logger.log(Level.FINE, "Retry attemp failed. Giving up{0}", timedOut ? " due to time-out." : "."); invocation.metrics.incrementRetryCallsFailedTotal(); throw ex; } + logger.log(Level.FINE, "Retry attempt failed. {0} attempts left.", attemptsLeft); if (retry.isDelayed()) { invocation.service.delay(retry.jitteredDelay(), invocation.context); } @@ -341,6 +374,7 @@ private Object processCircuitBreakerStage(FaultToleranceInvocation invocation, C if (!isCircuitBreakerPresent()) { return processTimeoutStage(invocation, asyncAttempt); } + logger.log(Level.FINER, "Proceeding invocation with circuitbreaker semantics"); CircuitBreakerState state = invocation.service.getState(circuitBreaker.requestVolumeThreshold, invocation.context); if (isMetricsEnabled) { invocation.metrics.insertCircuitbreakerOpenTotal(state::nanosOpen); @@ -351,36 +385,43 @@ private Object processCircuitBreakerStage(FaultToleranceInvocation invocation, C switch (state.getCircuitState()) { default: case OPEN: + logger.log(Level.FINER, "CircuitBreaker is open, throwing exception"); invocation.metrics.incrementCircuitbreakerCallsPreventedTotal(); throw new CircuitBreakerOpenException(); case HALF_OPEN: + logger.log(Level.FINER, "Proceeding half open CircuitBreaker context"); try { resultValue = processTimeoutStage(invocation, asyncAttempt); } catch (Exception ex) { invocation.metrics.incrementCircuitbreakerCallsFailedTotal(); if (circuitBreaker.failOn(ex)) { + logger.log(Level.FINE, "Exception causes CircuitBreaker to transit: half-open => open"); invocation.metrics.incrementCircuitbreakerOpenedTotal(); state.open(); invocation.service.scheduleDelayed(circuitBreaker.delay, state::halfOpen); } throw ex; } - state.halfOpenSuccessful(circuitBreaker.successThreshold); + if (state.halfOpenSuccessfulClosedCircuit(circuitBreaker.successThreshold)) { + logger.log(Level.FINE, "Success threshold causes CircuitBreaker to transit: half-open => closed"); + } invocation.metrics.incrementCircuitbreakerCallsSucceededTotal(); return resultValue; case CLOSED: + logger.log(Level.FINER, "Proceeding closed CircuitBreaker context"); Exception failedOn = null; try { resultValue = processTimeoutStage(invocation, asyncAttempt); - state.recordClosedResult(true); + state.recordClosedOutcome(true); } catch (Exception ex) { invocation.metrics.incrementCircuitbreakerCallsFailedTotal(); if (circuitBreaker.failOn(ex)) { - state.recordClosedResult(false); + state.recordClosedOutcome(false); } failedOn = ex; } if (state.isOverFailureThreshold(circuitBreaker.requestVolumeThreshold, circuitBreaker.failureRatio)) { + logger.log(Level.FINE, "Failure threshold causes CircuitBreaker to transit: closed => open"); invocation.metrics.incrementCircuitbreakerOpenedTotal(); state.open(); invocation.service.scheduleDelayed(circuitBreaker.delay, state::halfOpen); @@ -400,12 +441,14 @@ private Object processTimeoutStage(FaultToleranceInvocation invocation, Completa if (!isTimeoutPresent()) { return processBulkheadStage(invocation); } + logger.log(Level.FINER, "Proceeding invocation with timeout semantics"); long timeoutDuration = Duration.of(timeout.value, timeout.unit).toMillis(); long timeoutTime = System.currentTimeMillis() + timeoutDuration; Thread current = Thread.currentThread(); - AtomicBoolean didTimeout = new AtomicBoolean(false); + AtomicBoolean timedOut = new AtomicBoolean(false); Future timeout = invocation.service.scheduleDelayed(timeoutDuration, () -> { - didTimeout.set(true); + logger.log(Level.FINE, "Interrupting attempt due to timeout."); + timedOut.set(true); current.interrupt(); invocation.metrics.incrementTimeoutCallsTimedOutTotal(); if (asyncAttempt != null) { @@ -419,15 +462,18 @@ private Object processTimeoutStage(FaultToleranceInvocation invocation, Completa if (current.isInterrupted()) { Thread.interrupted(); // clear the flag } - if (didTimeout.get() || System.currentTimeMillis() > timeoutTime) { + if (timedOut.get() || System.currentTimeMillis() > timeoutTime) { throw new TimeoutException(); } invocation.metrics.incrementTimeoutCallsNotTimedOutTotal(); return resultValue; + } catch (TimeoutException ex) { + logger.log(Level.FINE, "Execution timed out."); + throw ex; } catch (Exception ex) { - if ((ex instanceof InterruptedException || ex.getCause() instanceof InterruptedException) - && System.currentTimeMillis() > timeoutTime) { - throw new TimeoutException(ex); + if (timedOut.get() || System.currentTimeMillis() > timeoutTime) { + logger.log(Level.FINE, "Execution timed out."); + throw new TimeoutException(ex); } throw ex; } finally { @@ -443,6 +489,7 @@ private Object processBulkheadStage(FaultToleranceInvocation invocation) throws if (!isBulkheadPresent()) { return proceed(invocation); } + logger.log(Level.FINER, "Proceeding invocation with bulkhead semantics"); InvocationContext context = invocation.context; BulkheadSemaphore concurrentExecutions = invocation.service.getConcurrentExecutions(bulkhead.value, context); BulkheadSemaphore waitingQueuePopulation = !isAsynchronous() ? null @@ -454,7 +501,9 @@ private Object processBulkheadStage(FaultToleranceInvocation invocation) throws } } long executionStartTime = System.nanoTime(); - if (concurrentExecutions.tryAcquire(0, TimeUnit.SECONDS)) { + logger.log(Level.FINER, "Attempting to acquire bulkhead execution permit."); + if (concurrentExecutions.tryAcquireFair()) { + logger.log(Level.FINE, "Acquired bulkhead execution permit."); invocation.metrics.incrementBulkheadCallsAcceptedTotal(); if (isAsynchronous()) { invocation.metrics.addBulkheadWaitingDuration(0L); // we did not wait but need to factor in the invocation for histogram quartiles @@ -471,15 +520,21 @@ private Object processBulkheadStage(FaultToleranceInvocation invocation) throws throw new BulkheadException("No free work permits."); } // from here: queueing style: - if (waitingQueuePopulation.tryAcquire(0, TimeUnit.SECONDS)) { + logger.log(Level.FINER, "Attempting to acquire bulkhead queue permit."); + if (waitingQueuePopulation.tryAcquireFair()) { + logger.log(Level.FINE, "Acquired bulkhead queue permit."); invocation.metrics.incrementBulkheadCallsAcceptedTotal(); long queueStartTime = System.nanoTime(); try { + invocation.trace("obtainBulkheadSemaphore"); concurrentExecutions.acquire(); // block until execution permit becomes available } catch (InterruptedException ex) { + logger.log(Level.FINE, "Interrupted acquiring bulkhead permit", ex); + invocation.metrics.incrementBulkheadCallsRejectedTotal(); waitingQueuePopulation.release(); throw new BulkheadException(ex); } finally { + invocation.endTrace(); invocation.metrics.addBulkheadWaitingDuration(System.nanoTime() - queueStartTime); } waitingQueuePopulation.release(); @@ -498,6 +553,7 @@ private Object processBulkheadStage(FaultToleranceInvocation invocation) throws */ private static Object proceed(FaultToleranceInvocation invocation) throws Exception { invocation.timeoutIfConcludedConcurrently(); + logger.log(Level.FINER, "Proceeding invocation chain"); return invocation.context.proceed(); } diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/StaticAnalysisContext.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/StaticAnalysisContext.java index e37cfdce1c3..90630fa4e33 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/StaticAnalysisContext.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/StaticAnalysisContext.java @@ -6,6 +6,11 @@ import javax.interceptor.InvocationContext; +/** + * A {@link InvocationContext} used during static analysis. + * + * @author Jan Bernitt + */ final class StaticAnalysisContext implements InvocationContext { private final Class targetClass; diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/service/FaultToleranceServiceImpl.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/service/FaultToleranceServiceImpl.java index 9efead6c993..3342d56a1c1 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/service/FaultToleranceServiceImpl.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/service/FaultToleranceServiceImpl.java @@ -305,7 +305,7 @@ public void delay(long delayMillis, InvocationContext context) throws Interrupte if (delayMillis <= 0) { return; } - startTrace("delayRetry", context); + trace("delayRetry", context); try { Thread.sleep(delayMillis); } finally { @@ -360,7 +360,7 @@ public Object fallbackInvoke(Method fallbackMethod, InvocationContext context) t } @Override - public void startTrace(String method, InvocationContext context) { + public void trace(String method, InvocationContext context) { startFaultToleranceSpan(new RequestTraceSpan(method), context); } diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/state/BulkheadSemaphore.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/state/BulkheadSemaphore.java index e0332d1e3f9..b092b9d22b0 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/state/BulkheadSemaphore.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/state/BulkheadSemaphore.java @@ -1,6 +1,7 @@ package fish.payara.microprofile.faulttolerance.state; import java.util.concurrent.Semaphore; +import java.util.concurrent.TimeUnit; public final class BulkheadSemaphore extends Semaphore { @@ -24,4 +25,16 @@ public int getTotalPermits() { public int acquiredPermits() { return totalPermits - availablePermits(); } + + /** + * Mainly exist to document the special semantics of {@link #tryAcquire(long, TimeUnit)} used that means trying to + * acquire a permit fair. + */ + public boolean tryAcquireFair() { + try { + return tryAcquire(0, TimeUnit.SECONDS); + } catch (InterruptedException e) { + return false; + } + } } diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/state/CircuitBreakerState.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/state/CircuitBreakerState.java index cb854008133..f4f06031465 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/state/CircuitBreakerState.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/state/CircuitBreakerState.java @@ -50,6 +50,7 @@ /** * Class that represents the state of a CircuitBreaker. * @author Andrew Pielage + * @author Jan Bernitt (2.0) */ public class CircuitBreakerState { @@ -99,7 +100,7 @@ public void setCircuitState(CircuitState circuitState) { * Records a success or failure result to the CircuitBreaker. * @param success True for a success, false for a failure */ - public void recordClosedResult(boolean success) { + public void recordClosedOutcome(boolean success) { // If the queue is full, remove the oldest result and add if (!this.closedResultsQueue.offer(success)) { this.closedResultsQueue.poll(); @@ -202,10 +203,12 @@ public void halfOpen() { setCircuitState(CircuitState.HALF_OPEN); } - public void halfOpenSuccessful(int successThreshold) { + public boolean halfOpenSuccessfulClosedCircuit(int successThreshold) { incrementHalfOpenSuccessfulResultCounter(); if (getHalfOpenSuccessFulResultCounter() == successThreshold) { close(); + return true; } + return false; } } diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/test/TestUtils.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/test/TestUtils.java index 084df3d1474..a517e40dc11 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/test/TestUtils.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/test/TestUtils.java @@ -4,6 +4,7 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import java.lang.reflect.Executable; import java.lang.reflect.Method; import org.eclipse.microprofile.faulttolerance.exceptions.FaultToleranceDefinitionException; @@ -12,7 +13,7 @@ public class TestUtils { - public static Object[] createNullArgumentsFor(Method method) { + public static Object[] createNullArgumentsFor(Executable method) { Object[] args = new Object[method.getParameterCount()]; for (int i = 0; i < method.getParameterCount(); i++) { if (method.getParameterTypes()[i].isPrimitive()) { From 77dc3d4deb0e81bab1397e78cae9fbe4e7f23ffc Mon Sep 17 00:00:00 2001 From: Jan Bernitt Date: Thu, 18 Apr 2019 16:00:04 +0200 Subject: [PATCH 12/30] PAYARA-3468 removed obsolete interceptors and validators, added javadoc, some renames --- .../faulttolerance/FaultToleranceConfig.java | 12 +- .../faulttolerance/FaultToleranceMetrics.java | 85 +++-- .../faulttolerance/FaultToleranceService.java | 53 ++- .../interceptors/AsynchronousInterceptor.java | 117 ------- .../BaseFaultToleranceInterceptor.java | 59 ---- .../interceptors/BulkheadInterceptor.java | 295 ---------------- .../CircuitBreakerInterceptor.java | 288 --------------- .../interceptors/RetryInterceptor.java | 328 ------------------ .../interceptors/TimeoutInterceptor.java | 191 ---------- .../interceptors/fallback/FallbackPolicy.java | 113 ------ .../policy/FaultTolerancePolicy.java | 10 +- .../FaultToleranceApplicationState.java | 2 +- .../FaultToleranceExecutionContext.java | 6 +- .../service/FaultToleranceMetricsFactory.java | 6 +- .../service/FaultToleranceServiceImpl.java | 31 +- .../service/FaultToleranceUtils.java | 49 --- .../validators/AsynchronousValidator.java | 67 ---- .../validators/BulkheadValidator.java | 83 ----- .../validators/CircuitBreakerValidator.java | 113 ------ .../validators/FallbackValidator.java | 107 ------ .../validators/RetryValidator.java | 112 ------ .../validators/TimeoutValidator.java | 66 ---- 22 files changed, 124 insertions(+), 2069 deletions(-) delete mode 100644 appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/AsynchronousInterceptor.java delete mode 100644 appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/BaseFaultToleranceInterceptor.java delete mode 100644 appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/BulkheadInterceptor.java delete mode 100644 appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/CircuitBreakerInterceptor.java delete mode 100644 appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/RetryInterceptor.java delete mode 100644 appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/TimeoutInterceptor.java delete mode 100644 appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/fallback/FallbackPolicy.java rename appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/{ => service}/FaultToleranceExecutionContext.java (74%) delete mode 100644 appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/validators/AsynchronousValidator.java delete mode 100644 appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/validators/BulkheadValidator.java delete mode 100644 appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/validators/CircuitBreakerValidator.java delete mode 100644 appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/validators/FallbackValidator.java delete mode 100644 appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/validators/RetryValidator.java delete mode 100644 appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/validators/TimeoutValidator.java diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/FaultToleranceConfig.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/FaultToleranceConfig.java index ec7b6bfdfaf..94ca2249de0 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/FaultToleranceConfig.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/FaultToleranceConfig.java @@ -18,6 +18,8 @@ * that the processing can be declared independent of the actual resolution mechanism. * * The default implementations provided will extract properties plain from the given annotations. + * + * @author Jan Bernitt */ @FunctionalInterface public interface FaultToleranceConfig { @@ -66,7 +68,7 @@ default int interceptorPriority() { /* - * Retry + * @Retry */ default int maxRetries(Retry annotation) { @@ -107,7 +109,7 @@ default Class[] abortOn(Retry annotation) { /* - * Circuit-Breaker + * @CircuitBreaker */ default Class[] failOn(CircuitBreaker annotation) { @@ -136,7 +138,7 @@ default int successThreshold(CircuitBreaker annotation) { /* - * Bulkhead + * @Bulkhead */ default int value(Bulkhead annotation) { @@ -149,7 +151,7 @@ default int waitingTaskQueue(Bulkhead annotation) { /* - * Timeout + * @Timeout */ default long value(Timeout annotation) { @@ -162,7 +164,7 @@ default ChronoUnit unit(Timeout annotation) { /* - * Fallback + * @Fallback */ default Class> value(Fallback annotation) { diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/FaultToleranceMetrics.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/FaultToleranceMetrics.java index 3f0aabea2e2..57dd9cb23b8 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/FaultToleranceMetrics.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/FaultToleranceMetrics.java @@ -3,13 +3,16 @@ import java.util.function.LongSupplier; /** - * Encodes the specifics of the FT metrics names using default methods while decoupling - * {@link org.eclipse.microprofile.metrics.MetricRegistry}. + * Encodes the specifics of the FT metrics names using default methods while decoupling rest of the implementation from + * the {@link org.eclipse.microprofile.metrics.MetricRegistry}. * * @author Jan Bernitt */ public interface FaultToleranceMetrics { + /** + * Can be used as NULL object when metrics are disabled so avoid testing for enabled but still do essentially NOOPs. + */ FaultToleranceMetrics DISABLED = new FaultToleranceMetrics() { /* does nothing */ }; /* @@ -19,35 +22,29 @@ public interface FaultToleranceMetrics { /** * Counters: * - * @param metric - * @param annotationType - * @param context + * @param metric full name of the metric with {@code %s} used as placeholder for the method name */ - default void increment(String metric) { + default void incrementCounter(String metric) { //NOOP (used when metrics are disabled) } /** * Histogram: * - * @param metric - * @param nanos - * @param annotationType - * @param context + * @param metric full name of the metric with {@code %s} used as placeholder for the method name + * @param nanos amount of nanoseconds to add to the histogram (>= 0) */ - default void add(String metric, long nanos) { + default void addToHistogram(String metric, long nanos) { //NOOP (used when metrics are disabled) } /** * Gauge: * - * @param metric - * @param gauge - * @param annotationType - * @param context + * @param metric full name of the metric with{@code %s} used as placeholder for the method name + * @param gauge the gauge function to use in case the gauge is not already linked */ - default void insert(String metric, LongSupplier gauge) { + default void linkGauge(String metric, LongSupplier gauge) { //NOOP (used when metrics are disabled) } @@ -57,11 +54,11 @@ default void insert(String metric, LongSupplier gauge) { */ default void incrementInvocationsTotal() { - increment("ft.%s.invocations.total"); + incrementCounter("ft.%s.invocations.total"); } default void incrementInvocationsFailedTotal() { - increment("ft.%s.invocations.failed.total"); + incrementCounter("ft.%s.invocations.failed.total"); } @@ -70,19 +67,19 @@ default void incrementInvocationsFailedTotal() { */ default void incrementRetryCallsSucceededNotRetriedTotal() { - increment("ft.%s.retry.callsSucceededNotRetried.total"); + incrementCounter("ft.%s.retry.callsSucceededNotRetried.total"); } default void incrementRetryCallsSucceededRetriedTotal() { - increment("ft.%s.retry.callsSucceededRetried.total"); + incrementCounter("ft.%s.retry.callsSucceededRetried.total"); } default void incrementRetryCallsFailedTotal() { - increment("ft.%s.retry.callsFailed.total"); + incrementCounter("ft.%s.retry.callsFailed.total"); } default void incrementRetryRetriesTotal() { - increment("ft.%s.retry.retries.total"); + incrementCounter("ft.%s.retry.retries.total"); } @@ -91,15 +88,15 @@ default void incrementRetryRetriesTotal() { */ default void addTimeoutExecutionDuration(long duration) { - add("ft.%s.timeout.executionDuration", duration); + addToHistogram("ft.%s.timeout.executionDuration", duration); } default void incrementTimeoutCallsTimedOutTotal() { - increment("ft.%s.timeout.callsTimedOut.total"); + incrementCounter("ft.%s.timeout.callsTimedOut.total"); } default void incrementTimeoutCallsNotTimedOutTotal() { - increment("ft.%s.timeout.callsNotTimedOut.total"); + incrementCounter("ft.%s.timeout.callsNotTimedOut.total"); } @@ -108,31 +105,31 @@ default void incrementTimeoutCallsNotTimedOutTotal() { */ default void incrementCircuitbreakerCallsSucceededTotal() { - increment("ft.%s.circuitbreaker.callsSucceeded.total"); + incrementCounter("ft.%s.circuitbreaker.callsSucceeded.total"); } default void incrementCircuitbreakerCallsFailedTotal() { - increment("ft.%s.circuitbreaker.callsFailed.total"); + incrementCounter("ft.%s.circuitbreaker.callsFailed.total"); } default void incrementCircuitbreakerCallsPreventedTotal() { - increment("ft.%s.circuitbreaker.callsPrevented.total"); + incrementCounter("ft.%s.circuitbreaker.callsPrevented.total"); } default void incrementCircuitbreakerOpenedTotal() { - increment("ft.%s.circuitbreaker.opened.total"); + incrementCounter("ft.%s.circuitbreaker.opened.total"); } - default void insertCircuitbreakerOpenTotal(LongSupplier gauge) { - insert("ft.%s.circuitbreaker.open.total", gauge); + default void linkCircuitbreakerOpenTotal(LongSupplier gauge) { + linkGauge("ft.%s.circuitbreaker.open.total", gauge); } - default void insertCircuitbreakerHalfOpenTotal(LongSupplier gauge) { - insert("ft.%s.circuitbreaker.halfOpen.total", gauge); + default void linkCircuitbreakerHalfOpenTotal(LongSupplier gauge) { + linkGauge("ft.%s.circuitbreaker.halfOpen.total", gauge); } - default void insertCircuitbreakerClosedTotal(LongSupplier gauge) { - insert("ft.%s.circuitbreaker.closed.total", gauge); + default void linkCircuitbreakerClosedTotal(LongSupplier gauge) { + linkGauge("ft.%s.circuitbreaker.closed.total", gauge); } @@ -141,27 +138,27 @@ default void insertCircuitbreakerClosedTotal(LongSupplier gauge) { */ default void incrementBulkheadCallsAcceptedTotal() { - increment("ft.%s.bulkhead.callsAccepted.total"); + incrementCounter("ft.%s.bulkhead.callsAccepted.total"); } default void incrementBulkheadCallsRejectedTotal() { - increment("ft.%s.bulkhead.callsRejected.total"); + incrementCounter("ft.%s.bulkhead.callsRejected.total"); } - default void insertBulkheadConcurrentExecutions(LongSupplier gauge) { - insert("ft.%s.bulkhead.concurrentExecutions", gauge); + default void linkBulkheadConcurrentExecutions(LongSupplier gauge) { + linkGauge("ft.%s.bulkhead.concurrentExecutions", gauge); } - default void insertBulkheadWaitingQueuePopulation(LongSupplier gauge) { - insert("ft.%s.bulkhead.waitingQueue.population", gauge); + default void linkBulkheadWaitingQueuePopulation(LongSupplier gauge) { + linkGauge("ft.%s.bulkhead.waitingQueue.population", gauge); } default void addBulkheadExecutionDuration(long duration) { - add("ft.%s.bulkhead.executionDuration", duration); + addToHistogram("ft.%s.bulkhead.executionDuration", duration); } default void addBulkheadWaitingDuration(long duration) { - add("ft.%s.bulkhead.waiting.duration", duration); + addToHistogram("ft.%s.bulkhead.waiting.duration", duration); } @@ -170,6 +167,6 @@ default void addBulkheadWaitingDuration(long duration) { */ default void incrementFallbackCallsTotal() { - increment("ft.%s.fallback.calls.total"); + incrementCounter("ft.%s.fallback.calls.total"); } } diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/FaultToleranceService.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/FaultToleranceService.java index 075efc52354..d753d43477a 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/FaultToleranceService.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/FaultToleranceService.java @@ -3,7 +3,9 @@ import java.lang.reflect.Method; import java.util.concurrent.Callable; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; import java.util.concurrent.Future; +import java.util.concurrent.RejectedExecutionException; import javax.interceptor.InvocationContext; @@ -13,35 +15,76 @@ import fish.payara.microprofile.faulttolerance.state.BulkheadSemaphore; import fish.payara.microprofile.faulttolerance.state.CircuitBreakerState; +/** + * Essentially a list of all methods needed to process FT behaviour. + * + * Decouples the FT processing facilities and state from any specific implementation to allow e.g. unit testing. + * + * @author Jan Bernitt + */ public interface FaultToleranceService { /* - * Factory methods: + * Factory methods */ FaultToleranceConfig getConfig(InvocationContext context, Stereotypes stereotypes); FaultToleranceMetrics getMetrics(InvocationContext context); + /* + * State + */ + CircuitBreakerState getState(int requestVolumeThreshold, InvocationContext context); BulkheadSemaphore getConcurrentExecutions(int maxConcurrentThreads, InvocationContext context); BulkheadSemaphore getWaitingQueuePopulation(int queueCapacity, InvocationContext context); + /* + * Processing + */ + + /** + * Delays the current thread by the given duration. The delay is traced. + * + * @param delayMillis the time to sleep in milliseconds + * @param context current context delayed + * @throws InterruptedException In case waiting is interrupted + */ void delay(long delayMillis, InvocationContext context) throws InterruptedException; - void runAsynchronous(CompletableFuture asyncResult, Callable operation) throws Exception; + /** + * Runs a given task after a certain waiting time. + * + * @param delayMillis time to wait in milliseconds before running the given task + * @param task operation to run + * @return A future that can be cancelled if the operation should no longer be run + */ + Future scheduleDelayed(long delayMillis, Runnable task) throws Exception; /** - * @return A future that can be cancelled if the method execution completes before the interrupt happens + * Runs the task asynchronously and completes the given asyncResult with the its outcome. + * + * @param asyncResult a not yet completed {@link CompletableFuture} that should receive the result of the operation + * when it is executed + * @param task an operation that must compute a value of type {@link Future} or {@link CompletionStage}. + * @throws RejectedExecutionException In case the task could not be accepted for execution. Usually due to too many + * work in progress. */ - Future scheduleDelayed(long delayMillis, Runnable operation) throws Exception; + void runAsynchronous(CompletableFuture asyncResult, Callable task) + throws RejectedExecutionException; - Object fallbackHandle(Class> fallbackClass, InvocationContext context, Exception exception) throws Exception; + Object fallbackHandle(Class> fallbackClass, InvocationContext context, Exception ex) + throws Exception; Object fallbackInvoke(Method fallbackMethod, InvocationContext context) throws Exception; + /* + * Tracing + */ + void trace(String method, InvocationContext context); void endTrace(); diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/AsynchronousInterceptor.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/AsynchronousInterceptor.java deleted file mode 100644 index 5e5e149d55d..00000000000 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/AsynchronousInterceptor.java +++ /dev/null @@ -1,117 +0,0 @@ -/* - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. - * - * Copyright (c) [2017] Payara Foundation and/or its affiliates. All rights reserved. - * - * The contents of this file are subject to the terms of either the GNU - * General Public License Version 2 only ("GPL") or the Common Development - * and Distribution License("CDDL") (collectively, the "License"). You - * may not use this file except in compliance with the License. You can - * obtain a copy of the License at - * https://github.com/payara/Payara/blob/master/LICENSE.txt - * See the License for the specific - * language governing permissions and limitations under the License. - * - * When distributing the software, include this License Header Notice in each - * file and include the License file at glassfish/legal/LICENSE.txt. - * - * GPL Classpath Exception: - * The Payara Foundation designates this particular file as subject to the "Classpath" - * exception as provided by the Payara Foundation in the GPL Version 2 section of the License - * file that accompanied this code. - * - * Modifications: - * If applicable, add the following below the License Header, with the fields - * enclosed by brackets [] replaced by your own identifying information: - * "Portions Copyright [year] [name of copyright owner]" - * - * Contributor(s): - * If you wish your version of this file to be governed by only the CDDL or - * only the GPL Version 2, indicate your decision by adding "[Contributor] - * elects to include this software in this distribution under the [CDDL or GPL - * Version 2] license." If you don't indicate a single choice of license, a - * recipient has the option to distribute your version of this file under - * either the CDDL, the GPL Version 2 or to extend the choice of license to - * its licensees as provided above. However, if you add GPL Version 2 code - * and therefore, elected the GPL Version 2 license, then the option applies - * only if the new code is made subject to such option by the copyright - * holder. - */ -package fish.payara.microprofile.faulttolerance.interceptors; - -import java.io.Serializable; -import java.util.concurrent.CompletionStage; -import java.util.concurrent.Future; -import java.util.logging.Level; -import javax.annotation.Priority; -import javax.interceptor.AroundInvoke; -import javax.interceptor.Interceptor; -import javax.interceptor.InvocationContext; -import javax.naming.NamingException; - -import org.eclipse.microprofile.faulttolerance.Asynchronous; -import org.eclipse.microprofile.faulttolerance.Fallback; - -/** - * Interceptor for the Fault Tolerance Asynchronous Annotation. Also contains the wrapper class for the Future outcome. - * - * @author Andrew Pielage - */ -@Interceptor -@Asynchronous -@Priority(Interceptor.Priority.PLATFORM_AFTER) -public class AsynchronousInterceptor extends BaseFaultToleranceInterceptor implements Serializable { - - public AsynchronousInterceptor() { - super(Asynchronous.class, false); - } - - @AroundInvoke - public Object intercept(InvocationContext context) throws Exception { - Object resultValue = null; - - try { - // Attempt to proceed the InvocationContext with Asynchronous semantics if Fault Tolerance is enabled for - // this method - if (getConfig().isNonFallbackEnabled() && getConfig().isEnabled(Asynchronous.class)) { - resultValue = asynchronous(context); - } else { - // If fault tolerance isn't enabled, just proceed as normal - logger.log(Level.FINE, "Fault Tolerance not enabled, proceeding normally without asynchronous."); - resultValue = context.proceed(); - } - } catch (Exception ex) { - // If an exception was thrown, check if the method is annotated with @Fallback - // We should only get here if executing synchronously, as the exception wouldn't get thrown in this thread - Fallback fallback = getConfig().getAnnotation(Fallback.class); - - // If the method was annotated with Fallback and the annotation is enabled, attempt it, otherwise just - // propagate the exception upwards - if (fallback != null && getConfig().isEnabled(Fallback.class)) { - logger.log(Level.FINE, "Fallback annotation found on method - falling back from Asynchronous"); - //FallbackPolicy fallbackPolicy = new FallbackPolicy(fallback, getConfig(), getExecution(), getMetrics(), context); - resultValue = null; // fallbackPolicy.fallback(context, ex); - } else { - throw ex; - } - } - - return resultValue; - } - - private Object asynchronous(InvocationContext context) throws Exception, NamingException { - Class returnType = context.getMethod().getReturnType(); - if (returnType == CompletionStage.class) { - logger.log(Level.FINER, "Proceeding invocation asynchronously"); - //TODO - return context.proceed(); - } - if (returnType == Future.class) { - logger.log(Level.FINER, "Proceeding invocation asynchronously"); - return null; //TODO run - } - logger.log(Level.SEVERE, "Unsupported return type for @Asynchronous annotated method: " + returnType - + ", proceeding normally without asynchronous."); - return context.proceed(); - } -} diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/BaseFaultToleranceInterceptor.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/BaseFaultToleranceInterceptor.java deleted file mode 100644 index 10a6dac6cb8..00000000000 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/BaseFaultToleranceInterceptor.java +++ /dev/null @@ -1,59 +0,0 @@ -package fish.payara.microprofile.faulttolerance.interceptors; - -import java.lang.annotation.Annotation; -import java.util.logging.Level; -import java.util.logging.Logger; - -import javax.interceptor.AroundInvoke; -import javax.interceptor.InvocationContext; - -import org.eclipse.microprofile.faulttolerance.Bulkhead; -import org.eclipse.microprofile.faulttolerance.CircuitBreaker; -import org.eclipse.microprofile.faulttolerance.Fallback; -import org.eclipse.microprofile.faulttolerance.Retry; - -import fish.payara.microprofile.faulttolerance.FaultToleranceConfig; -import fish.payara.microprofile.faulttolerance.FaultToleranceMetrics; -import fish.payara.microprofile.faulttolerance.FaultToleranceService; -import fish.payara.microprofile.faulttolerance.interceptors.fallback.FallbackPolicy; - -public abstract class BaseFaultToleranceInterceptor { - - protected final Logger logger; - private final String type; - private final Class annotationType; - private final boolean throwExceptionForRetry; - private FaultToleranceConfig config; - private FaultToleranceService execution; - private FaultToleranceMetrics metrics; - - protected BaseFaultToleranceInterceptor(Class annotationType, boolean throwExceptionForRetry) { - this.type = annotationType.getSimpleName(); - this.annotationType = annotationType; - this.throwExceptionForRetry = throwExceptionForRetry; - this.logger = Logger.getLogger(getClass().getName()); - } - - public void setConfig(FaultToleranceConfig behaviour) { - this.config = behaviour; - } - - public FaultToleranceConfig getConfig() { - return config; - } - - public FaultToleranceMetrics getMetrics() { - return metrics; - } - - public FaultToleranceService getExecution() { - return execution; - } - - @AroundInvoke - public Object intercept(InvocationContext context) throws Exception { - Object resultValue = null; - - return resultValue; - } -} diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/BulkheadInterceptor.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/BulkheadInterceptor.java deleted file mode 100644 index c3deb986e15..00000000000 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/BulkheadInterceptor.java +++ /dev/null @@ -1,295 +0,0 @@ -/* - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. - * - * Copyright (c) 2017-2018 Payara Foundation and/or its affiliates. All rights reserved. - * - * The contents of this file are subject to the terms of either the GNU - * General Public License Version 2 only ("GPL") or the Common Development - * and Distribution License("CDDL") (collectively, the "License"). You - * may not use this file except in compliance with the License. You can - * obtain a copy of the License at - * https://github.com/payara/Payara/blob/master/LICENSE.txt - * See the License for the specific - * language governing permissions and limitations under the License. - * - * When distributing the software, include this License Header Notice in each - * file and include the License file at glassfish/legal/LICENSE.txt. - * - * GPL Classpath Exception: - * The Payara Foundation designates this particular file as subject to the "Classpath" - * exception as provided by the Payara Foundation in the GPL Version 2 section of the License - * file that accompanied this code. - * - * Modifications: - * If applicable, add the following below the License Header, with the fields - * enclosed by brackets [] replaced by your own identifying information: - * "Portions Copyright [year] [name of copyright owner]" - * - * Contributor(s): - * If you wish your version of this file to be governed by only the CDDL or - * only the GPL Version 2, indicate your decision by adding "[Contributor] - * elects to include this software in this distribution under the [CDDL or GPL - * Version 2] license." If you don't indicate a single choice of license, a - * recipient has the option to distribute your version of this file under - * either the CDDL, the GPL Version 2 or to extend the choice of license to - * its licensees as provided above. However, if you add GPL Version 2 code - * and therefore, elected the GPL Version 2 license, then the option applies - * only if the new code is made subject to such option by the copyright - * holder. - */ -package fish.payara.microprofile.faulttolerance.interceptors; - -import fish.payara.microprofile.faulttolerance.interceptors.fallback.FallbackPolicy; -import fish.payara.microprofile.faulttolerance.state.BulkheadSemaphore; -import java.io.Serializable; -import java.util.concurrent.TimeUnit; -import java.util.logging.Level; -import javax.annotation.Priority; -import javax.interceptor.AroundInvoke; -import javax.interceptor.Interceptor; -import javax.interceptor.InvocationContext; - -import org.eclipse.microprofile.faulttolerance.Asynchronous; -import org.eclipse.microprofile.faulttolerance.Bulkhead; -import org.eclipse.microprofile.faulttolerance.Fallback; -import org.eclipse.microprofile.faulttolerance.Retry; -import org.eclipse.microprofile.faulttolerance.exceptions.BulkheadException; - -/** - * Interceptor for the Fault Tolerance Bulkhead Annotation. - * - * @author Andrew Pielage - * @author Jan Bernitt (2.0 update) - */ -@Interceptor -@Bulkhead -@Priority(Interceptor.Priority.PLATFORM_AFTER + 10) -public class BulkheadInterceptor extends BaseFaultToleranceInterceptor implements Serializable { - - public BulkheadInterceptor() { - super(Bulkhead.class, true); - } - - @AroundInvoke - public Object intercept(InvocationContext context) throws Exception { - Object resultValue = null; - - try { - // Attempt to proceed the InvocationContext with Asynchronous semantics if Fault Tolerance is enabled for this - // method - if (getConfig().isNonFallbackEnabled() && getConfig().isEnabled(Bulkhead.class)) { - if (getConfig().isMetricsEnabled()) { - // Only increment the invocations metric if the Retry annotation isn't present - if (getConfig().getAnnotation(Retry.class) == null) { - getMetrics().incrementInvocationsTotal(); - } - } - - - logger.log(Level.FINER, "Proceeding invocation with bulkhead semantics"); - resultValue = bulkhead(context); - } else { - // If fault tolerance isn't enabled, just proceed as normal - logger.log(Level.FINE, "Fault Tolerance not enabled, proceeding normally without bulkhead."); - resultValue = context.proceed(); - } - } catch (Exception ex) { - Retry retry = getConfig().getAnnotation(Retry.class); - - if (retry != null) { - logger.log(Level.FINE, "Retry annotation found on method, propagating error upwards."); - throw ex; - } - // If an exception was thrown, check if the method is annotated with @Fallback - Fallback fallback = getConfig().getAnnotation(Fallback.class); - - // If the method was annotated with Fallback and the annotation is enabled, attempt it, otherwise just - // propagate the exception upwards - if (fallback != null && getConfig().isEnabled(Fallback.class)) { - logger.log(Level.FINE, "Fallback annotation found on method, and no Retry annotation - " - + "falling back from Bulkhead"); - FallbackPolicy fallbackPolicy = new FallbackPolicy(fallback, getConfig(), getExecution(), getMetrics(), context); - resultValue = fallbackPolicy.fallback(context, ex); - } else { - logger.log(Level.FINE, "Fallback annotation not found on method, propagating error upwards.", ex); - - // Increment the failure counter metric - getMetrics().incrementInvocationsFailedTotal(); - throw ex; - } - } - - return resultValue; - } - - /** - * Proceeds the context under Bulkhead semantics. - * @param context The context to proceed. - * @return The outcome of the invocationContext - * @throws Exception - */ - private Object bulkhead(InvocationContext context) throws Exception { - Object resultValue = null; - - Bulkhead bulkhead = getConfig().getAnnotation(Bulkhead.class); - - BulkheadSemaphore bulkheadExecutionSemaphore = getExecution().getConcurrentExecutions(getConfig().value(bulkhead), context); - - if (getConfig().isMetricsEnabled()) { - getMetrics().insertBulkheadConcurrentExecutions(bulkheadExecutionSemaphore::acquiredPermits); - } - - // If the Asynchronous annotation is present, use threadpool style, otherwise use semaphore style - if (getConfig().getAnnotation(Asynchronous.class) != null) { - BulkheadSemaphore bulkheadExecutionQueueSemaphore = getExecution().getWaitingQueuePopulation(getConfig().waitingTaskQueue(bulkhead), context); - - if (getConfig().isMetricsEnabled()) { - getMetrics().insertBulkheadWaitingQueuePopulation(bulkheadExecutionQueueSemaphore::acquiredPermits); - } - - // Start measuring the queue duration for MP Metrics - long queueStartTime = System.nanoTime(); - - // Check if there are any free permits for concurrent execution - if (!bulkheadExecutionSemaphore.tryAcquire(0, TimeUnit.SECONDS)) { - logger.log(Level.FINER, "Attempting to acquire bulkhead queue semaphore."); - // If there aren't any free permits, see if there are any free queue permits - if (bulkheadExecutionQueueSemaphore.tryAcquire(0, TimeUnit.SECONDS)) { - logger.log(Level.FINER, "Acquired bulkhead queue semaphore."); - - // If there is a free queue permit, queue for an executor permit - try { - logger.log(Level.FINER, "Attempting to acquire bulkhead execution semaphore."); - getExecution().trace("obtainBulkheadSemaphore", context); - try { - bulkheadExecutionSemaphore.acquire(); - } finally { - // Make sure we end the trace right here - getExecution().endTrace(); - - // Record the queue time for MP Metrics - getMetrics().addBulkheadWaitingDuration(System.nanoTime() - queueStartTime); - } - - logger.log(Level.FINER, "Acquired bulkhead queue semaphore."); - - // Release the queue permit - bulkheadExecutionQueueSemaphore.release(); - - // Incremement the MP Metrics callsAccepted counter - getMetrics().incrementBulkheadCallsAcceptedTotal(); - - // Start measuring the execution duration for MP Metrics - long executionStartTime = System.nanoTime(); - - // Proceed the invocation and wait for the response - try { - logger.log(Level.FINER, "Proceeding bulkhead context"); - resultValue = context.proceed(); - } catch (Exception ex) { - logger.log(Level.FINE, "Exception proceeding Bulkhead context", ex); - - // Generic catch, as we need to release the semaphore permits - bulkheadExecutionSemaphore.release(); - bulkheadExecutionQueueSemaphore.release(); - - // Record the execution time for MP Metrics - getMetrics().addBulkheadExecutionDuration(System.nanoTime() - executionStartTime); - - // Let the exception propagate further up - we just want to release the semaphores - throw ex; - } - - // Record the execution time for MP Metrics - getMetrics().addBulkheadExecutionDuration(System.nanoTime() - executionStartTime); - - // Release the execution permit - bulkheadExecutionSemaphore.release(); - } catch (InterruptedException ex) { - // Incremement the MP Metrics callsRejected counter - getMetrics().incrementBulkheadCallsRejectedTotal(); - - logger.log(Level.INFO, "Interrupted acquiring bulkhead semaphore", ex); - throw new BulkheadException(ex); - } - } else { - // Incremement the MP Metrics callsRejected counter - getMetrics().incrementBulkheadCallsRejectedTotal(); - - throw new BulkheadException("No free work or queue permits."); - } - } else { - // Incremement the MP Metrics callsAccepted counter - getMetrics().incrementBulkheadCallsAcceptedTotal(); - - // Record the queue time for MP Metrics - getMetrics().addBulkheadWaitingDuration(System.nanoTime() - queueStartTime); - - // Start measuring the execution duration for MP Metrics - long executionStartTime = System.nanoTime(); - - // Proceed the invocation and wait for the response - try { - logger.log(Level.FINER, "Proceeding bulkhead context"); - resultValue = context.proceed(); - } catch (Exception ex) { - logger.log(Level.FINE, "Exception proceeding Bulkhead context", ex); - - // Generic catch, as we need to release the semaphore permits - bulkheadExecutionSemaphore.release(); - - // Record the execution time for MP Metrics - getMetrics().addBulkheadExecutionDuration(System.nanoTime() - executionStartTime); - - // Let the exception propagate further up - we just want to release the semaphores - throw ex; - } - - // Record the execution time for MP Metrics - getMetrics().addBulkheadExecutionDuration(System.nanoTime() - executionStartTime); - - // Release the permit - bulkheadExecutionSemaphore.release(); - } - } else { - // Try to get an execution permit - if (bulkheadExecutionSemaphore.tryAcquire(0, TimeUnit.SECONDS)) { - // Incremement the MP Metrics callsAccepted counter - getMetrics().incrementBulkheadCallsAcceptedTotal(); - - // Start measuring the execution duration for MP Metrics - long executionStartTime = System.nanoTime(); - - // Proceed the invocation and wait for the response - try { - logger.log(Level.FINER, "Proceeding bulkhead context"); - resultValue = context.proceed(); - } catch (Exception ex) { - logger.log(Level.FINE, "Exception proceeding Bulkhead context", ex); - - // Generic catch, as we need to release the semaphore permits - bulkheadExecutionSemaphore.release(); - - // Record the execution time for MP Metrics - getMetrics().addBulkheadExecutionDuration(System.nanoTime() - executionStartTime); - - // Let the exception propagate further up - we just want to release the semaphores - throw ex; - } - - // Record the execution time for MP Metrics - getMetrics().addBulkheadExecutionDuration(System.nanoTime() - executionStartTime); - - // Release the permit - bulkheadExecutionSemaphore.release(); - } else { - // Incremement the MP Metrics callsRejected counter - getMetrics().incrementBulkheadCallsRejectedTotal(); - - throw new BulkheadException("No free work permits."); - } - } - - return resultValue; - } -} diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/CircuitBreakerInterceptor.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/CircuitBreakerInterceptor.java deleted file mode 100644 index 10c232119be..00000000000 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/CircuitBreakerInterceptor.java +++ /dev/null @@ -1,288 +0,0 @@ -/* - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. - * - * Copyright (c) [2017-2018] Payara Foundation and/or its affiliates. All rights reserved. - * - * The contents of this file are subject to the terms of either the GNU - * General Public License Version 2 only ("GPL") or the Common Development - * and Distribution License("CDDL") (collectively, the "License"). You - * may not use this file except in compliance with the License. You can - * obtain a copy of the License at - * https://github.com/payara/Payara/blob/master/LICENSE.txt - * See the License for the specific - * language governing permissions and limitations under the License. - * - * When distributing the software, include this License Header Notice in each - * file and include the License file at glassfish/legal/LICENSE.txt. - * - * GPL Classpath Exception: - * The Payara Foundation designates this particular file as subject to the "Classpath" - * exception as provided by the Payara Foundation in the GPL Version 2 section of the License - * file that accompanied this code. - * - * Modifications: - * If applicable, add the following below the License Header, with the fields - * enclosed by brackets [] replaced by your own identifying information: - * "Portions Copyright [year] [name of copyright owner]" - * - * Contributor(s): - * If you wish your version of this file to be governed by only the CDDL or - * only the GPL Version 2, indicate your decision by adding "[Contributor] - * elects to include this software in this distribution under the [CDDL or GPL - * Version 2] license." If you don't indicate a single choice of license, a - * recipient has the option to distribute your version of this file under - * either the CDDL, the GPL Version 2 or to extend the choice of license to - * its licensees as provided above. However, if you add GPL Version 2 code - * and therefore, elected the GPL Version 2 license, then the option applies - * only if the new code is made subject to such option by the copyright - * holder. - */ -package fish.payara.microprofile.faulttolerance.interceptors; - -import fish.payara.microprofile.faulttolerance.interceptors.fallback.FallbackPolicy; -import fish.payara.microprofile.faulttolerance.state.CircuitBreakerState; -import org.eclipse.microprofile.faulttolerance.Bulkhead; -import org.eclipse.microprofile.faulttolerance.CircuitBreaker; -import org.eclipse.microprofile.faulttolerance.Fallback; -import org.eclipse.microprofile.faulttolerance.Retry; -import org.eclipse.microprofile.faulttolerance.exceptions.CircuitBreakerOpenException; - -import javax.annotation.Priority; -import javax.interceptor.AroundInvoke; -import javax.interceptor.Interceptor; -import javax.interceptor.InvocationContext; -import java.io.Serializable; -import java.time.Duration; -import java.time.temporal.ChronoUnit; -import java.util.logging.Level; - -/** - * - * @author Andrew Pielage - */ -@Interceptor -@CircuitBreaker -@Priority(Interceptor.Priority.PLATFORM_AFTER + 15) -public class CircuitBreakerInterceptor extends BaseFaultToleranceInterceptor implements Serializable { - - public CircuitBreakerInterceptor() { - super(CircuitBreaker.class, true); - } - - @AroundInvoke - public Object intercept(InvocationContext context) throws Exception { - Object resultValue = null; - - // Attempt to proceed the invocation with CircuitBreaker semantics if Fault Tolerance is enabled for this method - try { - // Attempt to proceed the InvocationContext with Asynchronous semantics if Fault Tolerance is enabled - if (getConfig().isNonFallbackEnabled() && getConfig().isEnabled(CircuitBreaker.class)) { - // Only increment the invocations metric if the Retry and Bulkhead annotations aren't present - if (getConfig().getAnnotation(Bulkhead.class) == null - && getConfig().getAnnotation(Retry.class) == null) { - getMetrics().incrementInvocationsTotal(); - } - - logger.log(Level.FINER, "Proceeding invocation with circuitbreaker semantics"); - resultValue = circuitBreak(context); - } else { - // If fault tolerance isn't enabled, just proceed as normal - logger.log(Level.FINE, "Fault Tolerance not enabled, proceeding normally without circuitbreaker."); - resultValue = context.proceed(); - } - } catch (Exception ex) { - Retry retry = getConfig().getAnnotation(Retry.class); - - if (retry != null) { - logger.log(Level.FINE, "Retry annotation found on method, propagating error upwards."); - throw ex; - } - Fallback fallback = getConfig().getAnnotation(Fallback.class); - - // Only fall back if the annotation hasn't been disabled - if (fallback != null && getConfig().isEnabled(Fallback.class)) { - logger.log(Level.FINE, "Fallback annotation found on method, and no Retry annotation - " - + "falling back from CircuitBreaker"); - FallbackPolicy fallbackPolicy = new FallbackPolicy(fallback, getConfig(), getExecution(), getMetrics(), context); - resultValue = fallbackPolicy.fallback(context, ex); - } else { - // Increment the failure counter metric - getMetrics().incrementInvocationsFailedTotal(); - throw ex; - } - } - - return resultValue; - } - - private Object circuitBreak(InvocationContext context) throws Exception { - Object resultValue = null; - - CircuitBreaker circuitBreaker = getConfig().getAnnotation(CircuitBreaker.class); - - Class[] failOn = getConfig().failOn(circuitBreaker); - long delay = getConfig().delay(circuitBreaker); - ChronoUnit delayUnit = getConfig().delayUnit(circuitBreaker); - int requestVolumeThreshold = getConfig().requestVolumeThreshold(circuitBreaker); - double failureRatio = getConfig().failureRatio(circuitBreaker); - int successThreshold = getConfig().successThreshold(circuitBreaker); - - long delayMillis = Duration.of(delay, delayUnit).toMillis(); - - CircuitBreakerState circuitBreakerState = getExecution().getState(requestVolumeThreshold, context); - - if (getConfig().isMetricsEnabled()) { - getMetrics().insertCircuitbreakerOpenTotal(circuitBreakerState::nanosOpen); - getMetrics().insertCircuitbreakerHalfOpenTotal(circuitBreakerState::nanosHalfOpen); - getMetrics().insertCircuitbreakerClosedTotal(circuitBreakerState::nanosClosed); - } - - switch (circuitBreakerState.getCircuitState()) { - case OPEN: - logger.log(Level.FINER, "CircuitBreaker is Open, throwing exception"); - getMetrics().incrementCircuitbreakerCallsPreventedTotal(); - - // If open, immediately throw an error - throw new CircuitBreakerOpenException("CircuitBreaker for method " - + context.getMethod().getName() + " is in state OPEN."); - case CLOSED: - // If closed, attempt to proceed the invocation context - try { - logger.log(Level.FINER, "Proceeding CircuitBreaker context"); - resultValue = context.proceed(); - } catch (Exception ex) { - logger.log(Level.FINE, "Exception executing CircuitBreaker context"); - - // Check if the exception is something that should record a failure - if (shouldFail(failOn, ex)) { - logger.log(Level.FINE, "Caught exception is included in CircuitBreaker failOn, " - + "recording failure against CircuitBreaker"); - // Add a failure result to the queue - circuitBreakerState.recordClosedOutcome(Boolean.FALSE); - getMetrics().incrementCircuitbreakerCallsFailedTotal(); - - // Calculate the failure ratio, and if we're over it, open the circuit - breakCircuitIfRequired( - Math.round(requestVolumeThreshold * failureRatio), - circuitBreakerState, delayMillis, context); - } - - // Finally, propagate the error upwards - throw ex; - } - - // If everything is bon, add a success value - circuitBreakerState.recordClosedOutcome(Boolean.TRUE); - getMetrics().incrementCircuitbreakerCallsSucceededTotal(); - - // Calculate the failure ratio, and if we're over it, open the circuit - breakCircuitIfRequired( - Math.round(requestVolumeThreshold * failureRatio), - circuitBreakerState, delayMillis, context); - break; - case HALF_OPEN: - // If half-open, attempt to proceed the invocation context - try { - logger.log(Level.FINER, "Proceeding half open CircuitBreaker context"); - resultValue = context.proceed(); - } catch (Exception ex) { - logger.log(Level.FINE, "Exception executing CircuitBreaker context"); - - // Check if the exception is something that should record a failure - if (shouldFail(failOn, ex)) { - logger.log(Level.FINE, "Caught exception is included in CircuitBreaker failOn, " - + "reopening half open circuit"); - - getMetrics().incrementCircuitbreakerCallsFailedTotal(); - - // Open the circuit again, and reset the half-open result counter - circuitBreakerState.setCircuitState(CircuitBreakerState.CircuitState.OPEN); - circuitBreakerState.resetHalfOpenSuccessfulResultCounter(); - getExecution().scheduleDelayed(delayMillis, circuitBreakerState::halfOpen); - } - - throw ex; - } - - // If the invocation context hasn't thrown an error, record a success - circuitBreakerState.incrementHalfOpenSuccessfulResultCounter(); - getMetrics().incrementCircuitbreakerCallsSucceededTotal(); - - logger.log(Level.FINER, "Number of consecutive successful circuitbreaker executions = {0}", - circuitBreakerState.getHalfOpenSuccessFulResultCounter()); - - // If we've hit the success threshold, close the circuit - if (circuitBreakerState.getHalfOpenSuccessFulResultCounter() == successThreshold) { - logger.log(Level.FINE, "Number of consecutive successful CircuitBreaker executions is above " - + "threshold {0}, closing circuit", successThreshold); - - circuitBreakerState.setCircuitState(CircuitBreakerState.CircuitState.CLOSED); - - // Reset the counter for when we next need to use it - circuitBreakerState.resetHalfOpenSuccessfulResultCounter(); - - // Reset the rolling results window - circuitBreakerState.resetResults(); - } - - break; - } - - return resultValue; - } - - /** - * Helper method that checks whether or not the given exception is included in the failOn parameter. - * @param failOn The array to check for the exception in. - * @param ex The exception to check - * @return True if the exception is included in the array - */ - private boolean shouldFail(Class[] failOn, Exception ex) { - boolean shouldFail = false; - - if (failOn[0] != Throwable.class) { - for (Class failureClass : failOn) { - if (ex.getClass() == failureClass) { - logger.log(Level.FINER, "Exception {0} matches a Throwable in failOn", - ex.getClass().getSimpleName()); - shouldFail = true; - break; - } - try { - // If we there isn't a direct match, check if the exception is a subclass - ex.getClass().asSubclass(failureClass); - shouldFail = true; - - logger.log(Level.FINER, "Exception {0} is a child of a Throwable in retryOn: {1}", - new String[]{ex.getClass().getSimpleName(), failureClass.getSimpleName()}); - break; - } catch (ClassCastException cce) { - // Om nom nom - } - } - } else { - shouldFail = true; - } - - return shouldFail; - } - - private void breakCircuitIfRequired(long failureThreshold, CircuitBreakerState circuitBreakerState, - long delayMillis, InvocationContext context) throws Exception { - // If we're over the failure threshold, open the circuit - if (circuitBreakerState.isOverFailureThreshold(0, 0d)) { - logger.log(Level.FINE, "CircuitBreaker is over failure threshold {0}, opening circuit", - failureThreshold); - - // Update the circuit state and metric timers - circuitBreakerState.setCircuitState(CircuitBreakerState.CircuitState.OPEN); - - // Update the opened metric counter - getMetrics().incrementCircuitbreakerOpenedTotal(); - - // Kick off a thread that will half-open the circuit after the specified delay - getExecution().scheduleDelayed(delayMillis, circuitBreakerState::halfOpen); - } - } - -} diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/RetryInterceptor.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/RetryInterceptor.java deleted file mode 100644 index d21f7891ec7..00000000000 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/RetryInterceptor.java +++ /dev/null @@ -1,328 +0,0 @@ -/* - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. - * - * Copyright (c) 2017-2018 Payara Foundation and/or its affiliates. All rights reserved. - * - * The contents of this file are subject to the terms of either the GNU - * General Public License Version 2 only ("GPL") or the Common Development - * and Distribution License("CDDL") (collectively, the "License"). You - * may not use this file except in compliance with the License. You can - * obtain a copy of the License at - * https://github.com/payara/Payara/blob/master/LICENSE.txt - * See the License for the specific - * language governing permissions and limitations under the License. - * - * When distributing the software, include this License Header Notice in each - * file and include the License file at glassfish/legal/LICENSE.txt. - * - * GPL Classpath Exception: - * The Payara Foundation designates this particular file as subject to the "Classpath" - * exception as provided by the Payara Foundation in the GPL Version 2 section of the License - * file that accompanied this code. - * - * Modifications: - * If applicable, add the following below the License Header, with the fields - * enclosed by brackets [] replaced by your own identifying information: - * "Portions Copyright [year] [name of copyright owner]" - * - * Contributor(s): - * If you wish your version of this file to be governed by only the CDDL or - * only the GPL Version 2, indicate your decision by adding "[Contributor] - * elects to include this software in this distribution under the [CDDL or GPL - * Version 2] license." If you don't indicate a single choice of license, a - * recipient has the option to distribute your version of this file under - * either the CDDL, the GPL Version 2 or to extend the choice of license to - * its licensees as provided above. However, if you add GPL Version 2 code - * and therefore, elected the GPL Version 2 license, then the option applies - * only if the new code is made subject to such option by the copyright - * holder. - */ -package fish.payara.microprofile.faulttolerance.interceptors; - -import fish.payara.microprofile.faulttolerance.interceptors.fallback.FallbackPolicy; -import java.time.Duration; -import java.time.temporal.ChronoUnit; -import java.util.concurrent.ThreadLocalRandom; -import java.util.logging.Level; -import javax.annotation.Priority; -import javax.interceptor.AroundInvoke; -import javax.interceptor.Interceptor; -import javax.interceptor.InvocationContext; - -import org.eclipse.microprofile.faulttolerance.Fallback; -import org.eclipse.microprofile.faulttolerance.Retry; - -/** - * - * @author Andrew Pielage - */ -@Interceptor -@Retry -@Priority(Interceptor.Priority.PLATFORM_AFTER + 5) -public class RetryInterceptor extends BaseFaultToleranceInterceptor { - - public RetryInterceptor() { - super(Retry.class, false); - } - - @AroundInvoke - public Object intercept(InvocationContext context) throws Exception { - Object resultValue = null; - - try { - // Attempt to proceed the InvocationContext with Asynchronous semantics if Fault Tolerance is enabled for this - // method - if (getConfig().isNonFallbackEnabled() && getConfig().isEnabled(Retry.class)) { - // Increment the invocations metric - getMetrics().incrementInvocationsTotal(); - - logger.log(Level.FINER, "Proceeding invocation with retry semantics"); - resultValue = retry(context); - } else { - // If fault tolerance isn't enabled, just proceed as normal - logger.log(Level.FINE, "Fault Tolerance not enabled, proceeding normally without retry."); - resultValue = context.proceed(); - } - } catch (Exception ex) { - Fallback fallback = getConfig().getAnnotation(Fallback.class); - - // Only fall back if the annotation hasn't been disabled - if (fallback != null && getConfig().isEnabled(Fallback.class)) { - logger.log(Level.FINE, "Fallback annotation found on method - falling back from Retry"); - FallbackPolicy fallbackPolicy = new FallbackPolicy(fallback, getConfig(), getExecution(), getMetrics(), context); - resultValue = fallbackPolicy.fallback(context, ex); - } else { - // Increment the failure counter metric - getMetrics().incrementInvocationsFailedTotal(); - throw ex; - } - } - - return resultValue; - } - - /** - * Proceeds the given invocation context with Retry semantics. - * @param context The invocation context to proceed - * @return The proceeded invocation context - * @throws Exception If the invocation throws an exception that shouldn't be retried, or if all retry attempts are - * expended - */ - private Object retry(InvocationContext context) throws Exception { - Object resultValue = null; - Retry retry = getConfig().getAnnotation(Retry.class); - - try { - resultValue = context.proceed(); - getMetrics().incrementRetryCallsSucceededNotRetriedTotal(); - } catch (Exception ex) { - final Class[] retryOn = getConfig().retryOn(retry); - final Class[] abortOn = getConfig().abortOn(retry); - - if (!shouldRetry(retryOn, abortOn, ex)) { - logger.log(Level.FINE, "Exception is contained in retryOn or abortOn, not retrying.", ex); - throw ex; - } - - int maxRetries = getConfig().maxRetries(retry); - long delay = getConfig().delay(retry); - ChronoUnit delayUnit = getConfig().delayUnit(retry); - long maxDuration = getConfig().maxDuration(retry); - ChronoUnit durationUnit = getConfig().durationUnit(retry); - long jitter = getConfig().jitter(retry); - ChronoUnit jitterDelayUnit = getConfig().jitterDelayUnit(retry); - - long delayMillis = Duration.of(delay, delayUnit).toMillis(); - long jitterMillis = Duration.of(jitter, jitterDelayUnit).toMillis(); - long timeoutTime = System.currentTimeMillis() + Duration.of(maxDuration, durationUnit).toMillis(); - - Exception retryException = ex; - - getExecution().trace("retryMethod", context); - - boolean succeeded = false; - - try { - if (maxRetries == -1 && maxDuration > 0) { - logger.log(Level.FINER, "Retrying until maxDuration is breached."); - - while (System.currentTimeMillis() < timeoutTime) { - getMetrics().incrementRetryRetriesTotal(); - try { - resultValue = context.proceed(); - succeeded = true; - getMetrics().incrementRetryCallsSucceededRetriedTotal(); - break; - } catch (Exception caughtException) { - retryException = caughtException; - if (!shouldRetry(retryOn, abortOn, caughtException)) { - break; - } - - if (delayMillis > 0 || jitterMillis > 0) { - getExecution().trace("delayRetry", context); - try { - Thread.sleep(delayMillis + ThreadLocalRandom.current().nextLong(0, jitterMillis)); - } finally { - getExecution().endTrace(); - } - } - } - } - } else if (maxRetries == -1 && maxDuration == 0) { - logger.log(Level.INFO, "Retrying potentially forever!"); - while (true) { - getMetrics().incrementRetryRetriesTotal(); - try { - resultValue = context.proceed(); - getMetrics().incrementRetryCallsSucceededRetriedTotal(); - succeeded = true; - break; - } catch (Exception caughtException) { - retryException = caughtException; - if (!shouldRetry(retryOn, abortOn, caughtException)) { - break; - } - - if (delayMillis > 0 || jitterMillis > 0) { - getExecution().trace("delayRetry", context); - try { - Thread.sleep(delayMillis + ThreadLocalRandom.current().nextLong(0, jitterMillis)); - } finally { - getExecution().endTrace(); - } - } - } - } - } else if (maxRetries != -1 && maxDuration > 0) { - logger.log(Level.INFO, - "Retrying as long as maxDuration ({0}ms) isn''t breached, and no more than {1} times", - new Object[]{Duration.of(maxDuration, durationUnit).toMillis(), maxRetries}); - while (maxRetries > 0 && System.currentTimeMillis() < timeoutTime) { - getMetrics().incrementRetryRetriesTotal(); - try { - resultValue = context.proceed(); - getMetrics().incrementRetryCallsSucceededRetriedTotal(); - succeeded = true; - break; - } catch (Exception caughtException) { - retryException = caughtException; - if (!shouldRetry(retryOn, abortOn, caughtException)) { - break; - } - - if (delayMillis > 0 || jitterMillis > 0) { - getExecution().trace("delayRetry", context); - try { - Thread.sleep(delayMillis + ThreadLocalRandom.current().nextLong(0, jitterMillis)); - } finally { - getExecution().endTrace(); - } - } - - maxRetries--; - } - } - } else { - logger.log(Level.INFO, "Retrying no more than {0} times", maxRetries); - while (maxRetries > 0) { - getMetrics().incrementRetryRetriesTotal(); - try { - resultValue = context.proceed(); - getMetrics().incrementRetryCallsSucceededRetriedTotal(); - succeeded = true; - break; - } catch (Exception caughtException) { - retryException = caughtException; - if (!shouldRetry(retryOn, abortOn, caughtException)) { - break; - } - - if (delayMillis > 0 || jitterMillis > 0) { - getExecution().trace("delayRetry", context); - try { - Thread.sleep(delayMillis + ThreadLocalRandom.current().nextLong(0, jitterMillis)); - } finally { - getExecution().endTrace(); - } - } - - maxRetries--; - } - } - } - } finally { - getExecution().endTrace(); - } - - if (!succeeded) { - getMetrics().incrementRetryCallsFailedTotal(); - throw retryException; - } - } - - return resultValue; - } - - /** - * Helper method that determines whether or not a retry should be attempted for the given exception. - * @param retryOn The exceptions to retry on. - * @param abortOn The exceptions to abort on. - * @param ex The caught exception - * @return True if retry should be attempted. - */ - private boolean shouldRetry(Class[] retryOn, Class[] abortOn, - Exception ex) { - boolean shouldRetry = false; - - // If the first value in the array is just "Exception", just set retry to true, otherwise check the exceptions - if (retryOn[0] != Exception.class) { - for (Class throwable : retryOn) { - if (ex.getClass() == throwable) { - logger.log(Level.FINER, "Exception {0} matches a Throwable in retryOn", - ex.getClass().getSimpleName()); - shouldRetry = true; - break; - } - try { - // If we there isn't a direct match, check if the exception is a subclass - ex.getClass().asSubclass(throwable); - shouldRetry = true; - - logger.log(Level.FINER, "Exception {0} is a child of a Throwable in retryOn: {1}", - new String[]{ex.getClass().getSimpleName(), throwable.getSimpleName()}); - break; - } catch (ClassCastException cce) { - // Om nom nom - } - } - } else { - shouldRetry = true; - } - - // If we should retry, check if the exception is one we shoukd abort on - if (shouldRetry && abortOn != null) { - for (Class throwable : abortOn) { - if (ex.getClass() == throwable) { - logger.log(Level.FINER, "Exception {0} matches a Throwable in abortOn", - ex.getClass().getSimpleName()); - shouldRetry = false; - break; - } - try { - // If we there isn't a direct match, check if the exception is a subclass - ex.getClass().asSubclass(throwable); - shouldRetry = false; - - logger.log(Level.FINER, "Exception {0} is a child of a Throwable in abortOn: {1}", - new String[]{ex.getClass().getSimpleName(), throwable.getSimpleName()}); - break; - } catch (ClassCastException cce) { - // Om nom nom - } - } - } - - return shouldRetry; - } -} diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/TimeoutInterceptor.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/TimeoutInterceptor.java deleted file mode 100644 index f97217ffc60..00000000000 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/TimeoutInterceptor.java +++ /dev/null @@ -1,191 +0,0 @@ -/* - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. - * - * Copyright (c) [2017] Payara Foundation and/or its affiliates. All rights reserved. - * - * The contents of this file are subject to the terms of either the GNU - * General Public License Version 2 only ("GPL") or the Common Development - * and Distribution License("CDDL") (collectively, the "License"). You - * may not use this file except in compliance with the License. You can - * obtain a copy of the License at - * https://github.com/payara/Payara/blob/master/LICENSE.txt - * See the License for the specific - * language governing permissions and limitations under the License. - * - * When distributing the software, include this License Header Notice in each - * file and include the License file at glassfish/legal/LICENSE.txt. - * - * GPL Classpath Exception: - * The Payara Foundation designates this particular file as subject to the "Classpath" - * exception as provided by the Payara Foundation in the GPL Version 2 section of the License - * file that accompanied this code. - * - * Modifications: - * If applicable, add the following below the License Header, with the fields - * enclosed by brackets [] replaced by your own identifying information: - * "Portions Copyright [year] [name of copyright owner]" - * - * Contributor(s): - * If you wish your version of this file to be governed by only the CDDL or - * only the GPL Version 2, indicate your decision by adding "[Contributor] - * elects to include this software in this distribution under the [CDDL or GPL - * Version 2] license." If you don't indicate a single choice of license, a - * recipient has the option to distribute your version of this file under - * either the CDDL, the GPL Version 2 or to extend the choice of license to - * its licensees as provided above. However, if you add GPL Version 2 code - * and therefore, elected the GPL Version 2 license, then the option applies - * only if the new code is made subject to such option by the copyright - * holder. - */ -package fish.payara.microprofile.faulttolerance.interceptors; - -import fish.payara.microprofile.faulttolerance.interceptors.fallback.FallbackPolicy; -import java.time.Duration; -import java.time.temporal.ChronoUnit; -import java.util.concurrent.Future; -import java.util.logging.Level; - -import javax.annotation.Priority; -import javax.interceptor.AroundInvoke; -import javax.interceptor.Interceptor; -import javax.interceptor.InvocationContext; -import org.eclipse.microprofile.faulttolerance.Bulkhead; -import org.eclipse.microprofile.faulttolerance.CircuitBreaker; -import org.eclipse.microprofile.faulttolerance.Fallback; -import org.eclipse.microprofile.faulttolerance.Retry; -import org.eclipse.microprofile.faulttolerance.Timeout; -import org.eclipse.microprofile.faulttolerance.exceptions.TimeoutException; - -/** - * - * @author Andrew Pielage - */ -@Interceptor -@Timeout -@Priority(Interceptor.Priority.PLATFORM_AFTER + 20) -public class TimeoutInterceptor extends BaseFaultToleranceInterceptor { - - public TimeoutInterceptor() { - super(Timeout.class, true); - } - - @AroundInvoke - public Object intercept(InvocationContext context) throws Exception { - Object resultValue = null; - - try { - // Attempt to proceed the InvocationContext with Asynchronous semantics if Fault Tolerance is enabled for this - // method - if (getConfig().isNonFallbackEnabled() && getConfig().isEnabled(Timeout.class)) { - // Only increment the invocations metric if the Retry, Bulkhead, and CircuitBreaker annotations aren't present - if (getConfig().getAnnotation(Bulkhead.class) == null - && getConfig().getAnnotation(Retry.class) == null - && getConfig().getAnnotation(CircuitBreaker.class) == null) { - getMetrics().incrementInvocationsTotal(); - } - - logger.log(Level.FINER, "Proceeding invocation with timeout semantics"); - resultValue = timeout(context); - } else { - // If fault tolerance isn't enabled, just proceed as normal - logger.log(Level.FINE, "Fault Tolerance not enabled, proceeding normally without timeout."); - resultValue = context.proceed(); - } - } catch (Exception ex) { - Retry retry = getConfig().getAnnotation(Retry.class); - - if (retry != null) { - logger.log(Level.FINE, "Retry annotation found on method, propagating error upwards."); - throw ex; - } - Fallback fallback = getConfig().getAnnotation(Fallback.class); - - // Only fall back if the annotation hasn't been disabled - if (fallback != null && getConfig().isEnabled(Fallback.class)) { - logger.log(Level.FINE, "Fallback annotation found on method, and no Retry annotation - " - + "falling back from Timeout"); - FallbackPolicy fallbackPolicy = new FallbackPolicy(fallback, getConfig(), getExecution(), getMetrics(), context); - resultValue = fallbackPolicy.fallback(context, ex); - } else { - // Increment the failure counter metric - getMetrics().incrementInvocationsFailedTotal(); - throw ex; - } - } - return resultValue; - } - - /** - * Proceeds the given invocation context with Timeout semantics. - * @param context The invocation context to proceed. - * @return The result of the invocation context. - * @throws Exception If the invocation context execution throws an exception - */ - private Object timeout(InvocationContext context) throws Exception { - Object resultValue = null; - - Timeout timeout = getConfig().getAnnotation(Timeout.class); - - long value = getConfig().value(timeout); - ChronoUnit unit = getConfig().unit(timeout); - - Future timeoutFuture = null; - long timeoutMillis = Duration.of(value, unit).toMillis(); - long timeoutTime = System.currentTimeMillis() + timeoutMillis; - long executionStartTime = System.nanoTime(); - - try { - timeoutFuture = getExecution().scheduleDelayed(timeoutMillis, Thread.currentThread()::interrupt); - resultValue = context.proceed(); - stopTimeout(timeoutFuture); - - if (System.currentTimeMillis() > timeoutTime) { - // Record the timeout for MP Metrics - getMetrics().incrementTimeoutCallsTimedOutTotal(); - logger.log(Level.FINE, "Execution timed out"); - throw new TimeoutException(); - } - - // Record the execution time for MP Metrics - getMetrics().addTimeoutExecutionDuration(System.nanoTime() - executionStartTime); - // Record the successfuly completion for MP Metrics - getMetrics().incrementTimeoutCallsNotTimedOutTotal(); - } catch (InterruptedException ie) { - // Record the execution time for MP Metrics - getMetrics().addTimeoutExecutionDuration(System.nanoTime() - executionStartTime); - - if (System.currentTimeMillis() > timeoutTime) { - // Record the timeout for MP Metrics - getMetrics().incrementTimeoutCallsTimedOutTotal(); - logger.log(Level.FINE, "Execution timed out"); - throw new TimeoutException(ie); - } - } catch (Exception ex) { - // Record the execution time for MP Metrics - getMetrics().addTimeoutExecutionDuration(System.nanoTime() - executionStartTime); - - // Deal with cases where someone has caught the thread.interrupt() and thrown the exception as something else - if (ex.getCause() instanceof InterruptedException && System.currentTimeMillis() > timeoutTime) { - // Record the timeout for MP Metrics - getMetrics().incrementTimeoutCallsTimedOutTotal(); - logger.log(Level.FINE, "Execution timed out"); - throw new TimeoutException(ex); - } - - stopTimeout(timeoutFuture); - throw ex; - } - - return resultValue; - } - - /** - * Helper method that stops the scheduled interrupt. - * @param timeoutFuture The scheduled interrupt to cancel. - */ - private static void stopTimeout(Future timeoutFuture) { - if (timeoutFuture != null) { - timeoutFuture.cancel(true); - } - } -} diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/fallback/FallbackPolicy.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/fallback/FallbackPolicy.java deleted file mode 100644 index 0846bda2558..00000000000 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/fallback/FallbackPolicy.java +++ /dev/null @@ -1,113 +0,0 @@ -/* - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. - * - * Copyright (c) 2017-2018 Payara Foundation and/or its affiliates. All rights reserved. - * - * The contents of this file are subject to the terms of either the GNU - * General Public License Version 2 only ("GPL") or the Common Development - * and Distribution License("CDDL") (collectively, the "License"). You - * may not use this file except in compliance with the License. You can - * obtain a copy of the License at - * https://github.com/payara/Payara/blob/master/LICENSE.txt - * See the License for the specific - * language governing permissions and limitations under the License. - * - * When distributing the software, include this License Header Notice in each - * file and include the License file at glassfish/legal/LICENSE.txt. - * - * GPL Classpath Exception: - * The Payara Foundation designates this particular file as subject to the "Classpath" - * exception as provided by the Payara Foundation in the GPL Version 2 section of the License - * file that accompanied this code. - * - * Modifications: - * If applicable, add the following below the License Header, with the fields - * enclosed by brackets [] replaced by your own identifying information: - * "Portions Copyright [year] [name of copyright owner]" - * - * Contributor(s): - * If you wish your version of this file to be governed by only the CDDL or - * only the GPL Version 2, indicate your decision by adding "[Contributor] - * elects to include this software in this distribution under the [CDDL or GPL - * Version 2] license." If you don't indicate a single choice of license, a - * recipient has the option to distribute your version of this file under - * either the CDDL, the GPL Version 2 or to extend the choice of license to - * its licensees as provided above. However, if you add GPL Version 2 code - * and therefore, elected the GPL Version 2 license, then the option applies - * only if the new code is made subject to such option by the copyright - * holder. - */ -package fish.payara.microprofile.faulttolerance.interceptors.fallback; - -import fish.payara.microprofile.faulttolerance.FaultToleranceConfig; -import fish.payara.microprofile.faulttolerance.FaultToleranceService; -import fish.payara.microprofile.faulttolerance.service.FaultToleranceUtils; -import fish.payara.microprofile.faulttolerance.FaultToleranceExecutionContext; -import fish.payara.microprofile.faulttolerance.FaultToleranceMetrics; - -import java.util.logging.Level; -import java.util.logging.Logger; -import javax.interceptor.InvocationContext; - -import org.eclipse.microprofile.faulttolerance.ExecutionContext; -import org.eclipse.microprofile.faulttolerance.Fallback; -import org.eclipse.microprofile.faulttolerance.FallbackHandler; -import javax.enterprise.inject.spi.CDI; - -/** - * Class that executes the fallback policy defined by the {@link Fallback} annotation. - * @author Andrew Pielage - */ -public class FallbackPolicy { - - private static final Logger logger = Logger.getLogger(FallbackPolicy.class.getName()); - - private final Class> fallbackClass; - private final String fallbackMethod; - private final FaultToleranceService execution; - private final FaultToleranceMetrics metrics; - - public FallbackPolicy(Fallback fallback, FaultToleranceConfig config, FaultToleranceService execution, FaultToleranceMetrics metrics, - InvocationContext context) { - this.execution = execution; - this.metrics = metrics; - this.fallbackClass = config.value(fallback); - this.fallbackMethod = config.fallbackMethod(fallback); - } - - /** - * Performs the fallback operation defined by the @Fallback annotation. - * @param context The failing invocation context - * @return The result of the executed fallback method - * @throws Exception If the fallback method itself fails. - */ - public Object fallback(InvocationContext context, Throwable exception) throws Exception { - Object resultValue = null; - execution.trace("executeFallbackMethod", context); - try { - if (fallbackMethod != null && !fallbackMethod.isEmpty()) { - logger.log(Level.FINE, "Using fallback method: {0}", fallbackMethod); - - resultValue = FaultToleranceUtils - .getAnnotatedMethodClass(context, Fallback.class) - .getDeclaredMethod(fallbackMethod, context.getMethod().getParameterTypes()) - .invoke(context.getTarget(), context.getParameters()); - } else { - logger.log(Level.FINE, "Using fallback class: {0}", fallbackClass.getName()); - - ExecutionContext executionContext = new FaultToleranceExecutionContext(context.getMethod(), - context.getParameters(), exception); - - resultValue = CDI.current().select(fallbackClass).get().handle(executionContext); - } - metrics.incrementFallbackCallsTotal(); - } catch (Exception ex) { - // Increment the failure counter metric - metrics.incrementInvocationsFailedTotal(); - throw ex; - } finally { - execution.endTrace(); - } - return resultValue; - } -} diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/FaultTolerancePolicy.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/FaultTolerancePolicy.java index 265f5fad5ac..728aa176ec0 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/FaultTolerancePolicy.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/FaultTolerancePolicy.java @@ -377,9 +377,9 @@ private Object processCircuitBreakerStage(FaultToleranceInvocation invocation, C logger.log(Level.FINER, "Proceeding invocation with circuitbreaker semantics"); CircuitBreakerState state = invocation.service.getState(circuitBreaker.requestVolumeThreshold, invocation.context); if (isMetricsEnabled) { - invocation.metrics.insertCircuitbreakerOpenTotal(state::nanosOpen); - invocation.metrics.insertCircuitbreakerHalfOpenTotal(state::nanosHalfOpen); - invocation.metrics.insertCircuitbreakerClosedTotal(state::nanosClosed); + invocation.metrics.linkCircuitbreakerOpenTotal(state::nanosOpen); + invocation.metrics.linkCircuitbreakerHalfOpenTotal(state::nanosHalfOpen); + invocation.metrics.linkCircuitbreakerClosedTotal(state::nanosClosed); } Object resultValue = null; switch (state.getCircuitState()) { @@ -495,9 +495,9 @@ private Object processBulkheadStage(FaultToleranceInvocation invocation) throws BulkheadSemaphore waitingQueuePopulation = !isAsynchronous() ? null : invocation.service.getWaitingQueuePopulation(bulkhead.waitingTaskQueue, context); if (isMetricsEnabled) { - invocation.metrics.insertBulkheadConcurrentExecutions(concurrentExecutions::acquiredPermits); + invocation.metrics.linkBulkheadConcurrentExecutions(concurrentExecutions::acquiredPermits); if (waitingQueuePopulation != null) { - invocation.metrics.insertBulkheadWaitingQueuePopulation(waitingQueuePopulation::acquiredPermits); + invocation.metrics.linkBulkheadWaitingQueuePopulation(waitingQueuePopulation::acquiredPermits); } } long executionStartTime = System.nanoTime(); diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/service/FaultToleranceApplicationState.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/service/FaultToleranceApplicationState.java index 22018a4f378..698221a43ff 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/service/FaultToleranceApplicationState.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/service/FaultToleranceApplicationState.java @@ -48,7 +48,7 @@ /** * @author Andrew Pielage andrew.pielage@payara.fish */ -public class FaultToleranceApplicationState { +final class FaultToleranceApplicationState { private final Map> circuitBreakerStates = new ConcurrentHashMap<>(); private final Map> bulkheadExecutionSemaphores = new ConcurrentHashMap<>(); diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/FaultToleranceExecutionContext.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/service/FaultToleranceExecutionContext.java similarity index 74% rename from appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/FaultToleranceExecutionContext.java rename to appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/service/FaultToleranceExecutionContext.java index a13f29eb8ed..c4592cab1e1 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/FaultToleranceExecutionContext.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/service/FaultToleranceExecutionContext.java @@ -1,4 +1,4 @@ -package fish.payara.microprofile.faulttolerance; +package fish.payara.microprofile.faulttolerance.service; import java.lang.reflect.Method; @@ -7,13 +7,13 @@ /** * Default implementation class for the Fault Tolerance ExecutionContext interface */ -public final class FaultToleranceExecutionContext implements ExecutionContext { +final class FaultToleranceExecutionContext implements ExecutionContext { private final Method method; private final Object[] parameters; private final Throwable failure; - public FaultToleranceExecutionContext(Method method, Object[] parameters, Throwable failure) { + FaultToleranceExecutionContext(Method method, Object[] parameters, Throwable failure) { this.method = method; this.parameters = parameters; this.failure = failure; diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/service/FaultToleranceMetricsFactory.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/service/FaultToleranceMetricsFactory.java index 49332bd25da..43382b6f96e 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/service/FaultToleranceMetricsFactory.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/service/FaultToleranceMetricsFactory.java @@ -68,17 +68,17 @@ FaultToleranceMetrics bindTo(InvocationContext context) { */ @Override - public void increment(String keyPattern) { + public void incrementCounter(String keyPattern) { metricRegistry.counter(metricName(keyPattern)).inc(); } @Override - public void add(String keyPattern, long duration) { + public void addToHistogram(String keyPattern, long duration) { metricRegistry.histogram(metricName(keyPattern)).update(duration); } @Override - public void insert(String keyPattern, LongSupplier gauge) { + public void linkGauge(String keyPattern, LongSupplier gauge) { String metricName = metricName(keyPattern); Gauge existingGauge = metricRegistry.getGauges().get(metricName); if (existingGauge == null) { diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/service/FaultToleranceServiceImpl.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/service/FaultToleranceServiceImpl.java index 3342d56a1c1..67f582a4d51 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/service/FaultToleranceServiceImpl.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/service/FaultToleranceServiceImpl.java @@ -40,7 +40,6 @@ package fish.payara.microprofile.faulttolerance.service; import fish.payara.microprofile.faulttolerance.FaultToleranceConfig; -import fish.payara.microprofile.faulttolerance.FaultToleranceExecutionContext; import fish.payara.microprofile.faulttolerance.FaultToleranceMetrics; import fish.payara.microprofile.faulttolerance.FaultToleranceService; import fish.payara.microprofile.faulttolerance.FaultToleranceServiceConfiguration; @@ -58,6 +57,7 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Future; +import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.TimeUnit; import java.util.logging.Level; import java.util.logging.Logger; @@ -255,9 +255,9 @@ private static String getFullMethodSignature(Method annotatedMethod) { + ">" + annotatedMethod.getReturnType().getSimpleName(); } - private void startFaultToleranceSpan(RequestTraceSpan span, InvocationContext invocationContext) { + private void startFaultToleranceSpan(RequestTraceSpan span, InvocationContext context) { if (requestTracingService != null && requestTracingService.isRequestTracingEnabled()) { - addGenericFaultToleranceRequestTracingDetails(span, invocationContext); + addGenericFaultToleranceRequestTracingDetails(span, context); requestTracingService.startTrace(span); } } @@ -269,12 +269,12 @@ private void endFaultToleranceSpan() { } private void addGenericFaultToleranceRequestTracingDetails(RequestTraceSpan span, - InvocationContext invocationContext) { + InvocationContext context) { span.addSpanTag("App Name", invocationManager.getCurrentInvocation().getAppName()); span.addSpanTag("Component ID", invocationManager.getCurrentInvocation().getComponentId()); span.addSpanTag("Module Name", invocationManager.getCurrentInvocation().getModuleName()); - span.addSpanTag("Class Name", invocationContext.getMethod().getDeclaringClass().getName()); - span.addSpanTag("Method Name", invocationContext.getMethod().getName()); + span.addSpanTag("Class Name", context.getMethod().getDeclaringClass().getName()); + span.addSpanTag("Method Name", context.getMethod().getName()); } @@ -314,11 +314,12 @@ public void delay(long delayMillis, InvocationContext context) throws Interrupte } @Override - public void runAsynchronous(CompletableFuture asyncResult, Callable operation) throws Exception { - Runnable task = () -> { + public void runAsynchronous(CompletableFuture asyncResult, Callable task) + throws RejectedExecutionException { + Runnable completionTask = () -> { if (!asyncResult.isCancelled() && !Thread.currentThread().isInterrupted()) { try { - Future futureResult = AsynchronousPolicy.toFuture(operation.call()); + Future futureResult = AsynchronousPolicy.toFuture(task.call()); if (!asyncResult.isCancelled()) { // could be cancelled in the meanwhile if (!asyncResult.isDone()) { asyncResult.complete(futureResult.get()); @@ -332,19 +333,19 @@ public void runAsynchronous(CompletableFuture asyncResult, Callable scheduleDelayed(long delayMillis, Runnable operation) throws Exception { - return getManagedScheduledExecutorService().schedule(operation, delayMillis, TimeUnit.MILLISECONDS); + public Future scheduleDelayed(long delayMillis, Runnable task) throws Exception { + return getManagedScheduledExecutorService().schedule(task, delayMillis, TimeUnit.MILLISECONDS); } @Override public Object fallbackHandle(Class> fallbackClass, InvocationContext context, - Exception exception) throws Exception { - return CDI.current().select(fallbackClass).get() - .handle(new FaultToleranceExecutionContext(context.getMethod(), context.getParameters(), exception)); + Exception ex) throws Exception { + return CDI.current().select(fallbackClass).get().handle( + new FaultToleranceExecutionContext(context.getMethod(), context.getParameters(), ex)); } @Override diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/service/FaultToleranceUtils.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/service/FaultToleranceUtils.java index 5118358f1d6..84dd575ce26 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/service/FaultToleranceUtils.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/service/FaultToleranceUtils.java @@ -109,55 +109,6 @@ public static A getAnnotation(Stereotypes sterotypes, Cla return null; } - /** - * Gets overriding config parameter values if they're present. - * @param The annotation type - * @param config The config to get the overriding parameter values from - * @param annotationClass The annotation class - * @param parameterName The name of the parameter to get the override value of - * @param annotatedMethodName The annotated method name - * @param annotatedClassCanonicalName The canonical name of the annotated method class - * @param parameterType The type of the parameter to get the override value of - * @return - */ - @Deprecated //TODO remove with validators - public static Optional getOverrideValue(Config config, Class annotationClass, - String parameterName, String annotatedMethodName, String annotatedClassCanonicalName, Class parameterType) { - Optional value = Optional.empty(); - - String annotationName = annotationClass.getSimpleName(); - - // Check if there's a config override for the method - if (config != null) { - logger.log(Level.FINER, "Getting config override for annotated method..."); - value = config.getOptionalValue(annotatedClassCanonicalName + "/" + annotatedMethodName - + "/" + annotationName + "/" + parameterName, parameterType); - - // If there wasn't a config override for the method, check if there's one for the class - if (!value.isPresent()) { - logger.log(Level.FINER, "No config override for annotated method, getting config override for the " - + "annotated class..."); - value = config.getOptionalValue(annotatedClassCanonicalName + "/" + annotationName - + "/" + parameterName, parameterType); - - // If there wasn't a config override for the class either, check if there's a global one - if (!value.isPresent()) { - logger.log(Level.FINER, "No config override for the annotated class, getting application wide " - + "config override..."); - value = config.getOptionalValue(annotationName + "/" + parameterName, parameterType); - - if (!value.isPresent()) { - logger.log(Level.FINER, "No config overrides"); - } - } - } - } else { - logger.log(Level.FINE, "No config to get override parameters from."); - } - - return value; - } - /** * Gets overriding config parameter values if they're present from an invocation context. * @param The annotation type diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/validators/AsynchronousValidator.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/validators/AsynchronousValidator.java deleted file mode 100644 index 55838257e39..00000000000 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/validators/AsynchronousValidator.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. - * - * Copyright (c) [2017] Payara Foundation and/or its affiliates. All rights reserved. - * - * The contents of this file are subject to the terms of either the GNU - * General Public License Version 2 only ("GPL") or the Common Development - * and Distribution License("CDDL") (collectively, the "License"). You - * may not use this file except in compliance with the License. You can - * obtain a copy of the License at - * https://github.com/payara/Payara/blob/master/LICENSE.txt - * See the License for the specific - * language governing permissions and limitations under the License. - * - * When distributing the software, include this License Header Notice in each - * file and include the License file at glassfish/legal/LICENSE.txt. - * - * GPL Classpath Exception: - * The Payara Foundation designates this particular file as subject to the "Classpath" - * exception as provided by the Payara Foundation in the GPL Version 2 section of the License - * file that accompanied this code. - * - * Modifications: - * If applicable, add the following below the License Header, with the fields - * enclosed by brackets [] replaced by your own identifying information: - * "Portions Copyright [year] [name of copyright owner]" - * - * Contributor(s): - * If you wish your version of this file to be governed by only the CDDL or - * only the GPL Version 2, indicate your decision by adding "[Contributor] - * elects to include this software in this distribution under the [CDDL or GPL - * Version 2] license." If you don't indicate a single choice of license, a - * recipient has the option to distribute your version of this file under - * either the CDDL, the GPL Version 2 or to extend the choice of license to - * its licensees as provided above. However, if you add GPL Version 2 code - * and therefore, elected the GPL Version 2 license, then the option applies - * only if the new code is made subject to such option by the copyright - * holder. - */ -package fish.payara.microprofile.faulttolerance.validators; - -import java.util.concurrent.CompletionStage; -import java.util.concurrent.Future; -import javax.enterprise.inject.spi.AnnotatedMethod; -import org.eclipse.microprofile.faulttolerance.Asynchronous; -import org.eclipse.microprofile.faulttolerance.exceptions.FaultToleranceDefinitionException; - -/** - * Validator for the Fault Tolerance Asynchronous annotation. - * @author Andrew Pielage - */ -public class AsynchronousValidator { - - /** - * Validates the given {@link Asynchronous} annotation. - * @param asynchronous The annotation to validate - * @param annotatedMethod The annotated method to validate - */ - public static void validateAnnotation(Asynchronous asynchronous, AnnotatedMethod annotatedMethod) { - Class returnType = annotatedMethod.getJavaMember().getReturnType(); - if (returnType != Future.class && returnType != CompletionStage.class) { - throw new FaultToleranceDefinitionException("Method \"" + annotatedMethod.getJavaMember().getName() + "\"" - + " annotated with " + Asynchronous.class.getCanonicalName() - + " does not return a Future or CompletionStage. Note that subtypes of these two are not permitted."); - } - } -} diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/validators/BulkheadValidator.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/validators/BulkheadValidator.java deleted file mode 100644 index 499f429d24a..00000000000 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/validators/BulkheadValidator.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. - * - * Copyright (c) [2017] Payara Foundation and/or its affiliates. All rights reserved. - * - * The contents of this file are subject to the terms of either the GNU - * General Public License Version 2 only ("GPL") or the Common Development - * and Distribution License("CDDL") (collectively, the "License"). You - * may not use this file except in compliance with the License. You can - * obtain a copy of the License at - * https://github.com/payara/Payara/blob/master/LICENSE.txt - * See the License for the specific - * language governing permissions and limitations under the License. - * - * When distributing the software, include this License Header Notice in each - * file and include the License file at glassfish/legal/LICENSE.txt. - * - * GPL Classpath Exception: - * The Payara Foundation designates this particular file as subject to the "Classpath" - * exception as provided by the Payara Foundation in the GPL Version 2 section of the License - * file that accompanied this code. - * - * Modifications: - * If applicable, add the following below the License Header, with the fields - * enclosed by brackets [] replaced by your own identifying information: - * "Portions Copyright [year] [name of copyright owner]" - * - * Contributor(s): - * If you wish your version of this file to be governed by only the CDDL or - * only the GPL Version 2, indicate your decision by adding "[Contributor] - * elects to include this software in this distribution under the [CDDL or GPL - * Version 2] license." If you don't indicate a single choice of license, a - * recipient has the option to distribute your version of this file under - * either the CDDL, the GPL Version 2 or to extend the choice of license to - * its licensees as provided above. However, if you add GPL Version 2 code - * and therefore, elected the GPL Version 2 license, then the option applies - * only if the new code is made subject to such option by the copyright - * holder. - */ -package fish.payara.microprofile.faulttolerance.validators; - -import javax.enterprise.inject.spi.AnnotatedMethod; -import org.eclipse.microprofile.config.Config; -import org.eclipse.microprofile.faulttolerance.Bulkhead; -import org.eclipse.microprofile.faulttolerance.exceptions.FaultToleranceDefinitionException; - -import fish.payara.microprofile.faulttolerance.service.FaultToleranceUtils; - -/** - * Validator for the Fault Tolerance Bulkhead annotation. - * @author Andrew Pielage - */ -public class BulkheadValidator { - - /** - * Validates the given Bulkhead annotation. - * @param bulkhead The annotation to validate - * @param annotatedMethod The annotated method to validate - * @param config The config to get any override values from - */ - public static void validateAnnotation(Bulkhead bulkhead, AnnotatedMethod annotatedMethod, Config config) { - int value = FaultToleranceUtils.getOverrideValue( - config, Bulkhead.class, "value", annotatedMethod.getJavaMember().getName(), - annotatedMethod.getJavaMember().getDeclaringClass().getCanonicalName(), Integer.class) - .orElse(bulkhead.value()); - int waitingTaskQueue = FaultToleranceUtils.getOverrideValue( - config, Bulkhead.class, "waitingTaskQueue", annotatedMethod.getJavaMember().getName(), - annotatedMethod.getJavaMember().getDeclaringClass().getCanonicalName(), Integer.class) - .orElse(bulkhead.waitingTaskQueue()); - - if (value < 1) { - throw new FaultToleranceDefinitionException("Method \"" + annotatedMethod.getJavaMember().getName() + "\"" - + " annotated with " + Bulkhead.class.getCanonicalName() - + " has a value less than 1: " + value); - } - - if (waitingTaskQueue < 0) { - throw new FaultToleranceDefinitionException("Method \"" + annotatedMethod.getJavaMember().getName() + "\"" - + " annotated with " + Bulkhead.class.getCanonicalName() - + " has a waitingTaskQueue value less than 0: " + waitingTaskQueue); - } - } -} diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/validators/CircuitBreakerValidator.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/validators/CircuitBreakerValidator.java deleted file mode 100644 index 52e4f815a3c..00000000000 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/validators/CircuitBreakerValidator.java +++ /dev/null @@ -1,113 +0,0 @@ -/* - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. - * - * Copyright (c) [2017] Payara Foundation and/or its affiliates. All rights reserved. - * - * The contents of this file are subject to the terms of either the GNU - * General Public License Version 2 only ("GPL") or the Common Development - * and Distribution License("CDDL") (collectively, the "License"). You - * may not use this file except in compliance with the License. You can - * obtain a copy of the License at - * https://github.com/payara/Payara/blob/master/LICENSE.txt - * See the License for the specific - * language governing permissions and limitations under the License. - * - * When distributing the software, include this License Header Notice in each - * file and include the License file at glassfish/legal/LICENSE.txt. - * - * GPL Classpath Exception: - * The Payara Foundation designates this particular file as subject to the "Classpath" - * exception as provided by the Payara Foundation in the GPL Version 2 section of the License - * file that accompanied this code. - * - * Modifications: - * If applicable, add the following below the License Header, with the fields - * enclosed by brackets [] replaced by your own identifying information: - * "Portions Copyright [year] [name of copyright owner]" - * - * Contributor(s): - * If you wish your version of this file to be governed by only the CDDL or - * only the GPL Version 2, indicate your decision by adding "[Contributor] - * elects to include this software in this distribution under the [CDDL or GPL - * Version 2] license." If you don't indicate a single choice of license, a - * recipient has the option to distribute your version of this file under - * either the CDDL, the GPL Version 2 or to extend the choice of license to - * its licensees as provided above. However, if you add GPL Version 2 code - * and therefore, elected the GPL Version 2 license, then the option applies - * only if the new code is made subject to such option by the copyright - * holder. - */ -package fish.payara.microprofile.faulttolerance.validators; - -import javax.enterprise.inject.spi.AnnotatedMethod; -import org.eclipse.microprofile.config.Config; -import org.eclipse.microprofile.faulttolerance.CircuitBreaker; -import org.eclipse.microprofile.faulttolerance.exceptions.FaultToleranceDefinitionException; - -import fish.payara.microprofile.faulttolerance.service.FaultToleranceUtils; - -/** - * Validator for the Fault Tolerance CircuitBreaker annotation. - * @author Andrew Pielage - */ -public class CircuitBreakerValidator { - - /** - * Validates the given CircuitBreaker annotation. - * @param circuitBreaker The annotation to validate - * @param annotatedMethod The annotated method to validate - * @param config The config to get any override values from - */ - public static void validateAnnotation(CircuitBreaker circuitBreaker, AnnotatedMethod annotatedMethod, - Config config) { - long delay = FaultToleranceUtils.getOverrideValue( - config, CircuitBreaker.class, "delay", annotatedMethod.getJavaMember().getName(), - annotatedMethod.getJavaMember().getDeclaringClass().getCanonicalName(), Long.class) - .orElse(circuitBreaker.delay()); - - int requestVolumeThreshold = FaultToleranceUtils.getOverrideValue( - config, CircuitBreaker.class, "requestVolumeThreshold", annotatedMethod.getJavaMember().getName(), - annotatedMethod.getJavaMember().getDeclaringClass().getCanonicalName(), Integer.class) - .orElse(circuitBreaker.requestVolumeThreshold()); - - double failureRatio = FaultToleranceUtils.getOverrideValue( - config, CircuitBreaker.class, "failureRatio", annotatedMethod.getJavaMember().getName(), - annotatedMethod.getJavaMember().getDeclaringClass().getCanonicalName(), Double.class) - .orElse(circuitBreaker.failureRatio()); - - int successThreshold = FaultToleranceUtils.getOverrideValue( - config, CircuitBreaker.class, "successThreshold", annotatedMethod.getJavaMember().getName(), - annotatedMethod.getJavaMember().getDeclaringClass().getCanonicalName(), Integer.class) - .orElse(circuitBreaker.successThreshold()); - - if (delay < 0) { - throw new FaultToleranceDefinitionException("Method \"" + annotatedMethod.getJavaMember().getName() + "\"" - + " annotated with " + CircuitBreaker.class.getCanonicalName() - + " has a delay value less than 0: " + delay); - } - - if (requestVolumeThreshold < 1) { - throw new FaultToleranceDefinitionException("Method \"" + annotatedMethod.getJavaMember().getName() + "\"" - + " annotated with " + CircuitBreaker.class.getCanonicalName() - + " has a requestVolumeThreshold value less than 1: " + requestVolumeThreshold); - } - - if (failureRatio < 0) { - throw new FaultToleranceDefinitionException("Method \"" + annotatedMethod.getJavaMember().getName() + "\"" - + " annotated with " + CircuitBreaker.class.getCanonicalName() - + " has a failureRatio value less than 0: " + failureRatio); - } - - if (failureRatio > 1) { - throw new FaultToleranceDefinitionException("Method \"" + annotatedMethod.getJavaMember().getName() + "\"" - + " annotated with " + CircuitBreaker.class.getCanonicalName() - + " has a failureRatio value greater than 1: " + failureRatio); - } - - if (successThreshold < 1) { - throw new FaultToleranceDefinitionException("Method \"" + annotatedMethod.getJavaMember().getName() + "\"" - + " annotated with " + CircuitBreaker.class.getCanonicalName() - + " has a successThreshold value less than 1: " + successThreshold); - } - } -} diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/validators/FallbackValidator.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/validators/FallbackValidator.java deleted file mode 100644 index 93f442e1a76..00000000000 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/validators/FallbackValidator.java +++ /dev/null @@ -1,107 +0,0 @@ -/* - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. - * - * Copyright (c) [2017] Payara Foundation and/or its affiliates. All rights reserved. - * - * The contents of this file are subject to the terms of either the GNU - * General Public License Version 2 only ("GPL") or the Common Development - * and Distribution License("CDDL") (collectively, the "License"). You - * may not use this file except in compliance with the License. You can - * obtain a copy of the License at - * https://github.com/payara/Payara/blob/master/LICENSE.txt - * See the License for the specific - * language governing permissions and limitations under the License. - * - * When distributing the software, include this License Header Notice in each - * file and include the License file at glassfish/legal/LICENSE.txt. - * - * GPL Classpath Exception: - * The Payara Foundation designates this particular file as subject to the "Classpath" - * exception as provided by the Payara Foundation in the GPL Version 2 section of the License - * file that accompanied this code. - * - * Modifications: - * If applicable, add the following below the License Header, with the fields - * enclosed by brackets [] replaced by your own identifying information: - * "Portions Copyright [year] [name of copyright owner]" - * - * Contributor(s): - * If you wish your version of this file to be governed by only the CDDL or - * only the GPL Version 2, indicate your decision by adding "[Contributor] - * elects to include this software in this distribution under the [CDDL or GPL - * Version 2] license." If you don't indicate a single choice of license, a - * recipient has the option to distribute your version of this file under - * either the CDDL, the GPL Version 2 or to extend the choice of license to - * its licensees as provided above. However, if you add GPL Version 2 code - * and therefore, elected the GPL Version 2 license, then the option applies - * only if the new code is made subject to such option by the copyright - * holder. - */ -package fish.payara.microprofile.faulttolerance.validators; - -import java.util.Optional; - -import javax.enterprise.inject.spi.AnnotatedMethod; -import org.eclipse.microprofile.config.Config; -import org.eclipse.microprofile.faulttolerance.ExecutionContext; -import org.eclipse.microprofile.faulttolerance.Fallback; -import org.eclipse.microprofile.faulttolerance.FallbackHandler; -import org.eclipse.microprofile.faulttolerance.exceptions.FaultToleranceDefinitionException; - -import fish.payara.microprofile.faulttolerance.service.FaultToleranceUtils; - -/** - * Validator for the Fault Tolerance Fallback Annotation. - * @author Andrew Pielage - */ -public class FallbackValidator { - - /** - * Validate that the Fallback annotation is correct. - * @param fallback The fallback annotation to validate - * @param annotatedMethod The method annotated with @Fallback - * @param config The config of the application - * @throws ClassNotFoundException If the fallbackClass could not be found - * @throws NoSuchMethodException If the fallbackMethod could not be found - */ - public static void validateAnnotation(Fallback fallback, AnnotatedMethod annotatedMethod, Config config) - throws ClassNotFoundException, NoSuchMethodException { - // Get the fallbackMethod - String fallbackMethod = FaultToleranceUtils.getOverrideValue( - config, Fallback.class, "fallbackMethod", annotatedMethod.getJavaMember().getName(), - annotatedMethod.getJavaMember().getDeclaringClass().getCanonicalName(), String.class) - .orElse(fallback.fallbackMethod()); - - // Get the fallbackClass, and check that it can be found - Optional fallbackClassName = FaultToleranceUtils - .getOverrideValue(config, Fallback.class, "value", annotatedMethod.getJavaMember().getName(), - annotatedMethod.getJavaMember().getDeclaringClass().getCanonicalName(), String.class); - @SuppressWarnings("unchecked") - Class> fallbackClass = fallbackClassName.isPresent() - ? (Class>) Thread.currentThread().getContextClassLoader() - .loadClass(fallbackClassName.get()) - : fallback.value(); - - // Validate the annotated method - if (fallbackMethod != null && !fallbackMethod.isEmpty()) { - if (fallbackClass != null && fallbackClass != Fallback.DEFAULT.class) { - throw new FaultToleranceDefinitionException("Both a fallback class and method have been set."); - } - try { - if (annotatedMethod.getJavaMember().getDeclaringClass().getDeclaredMethod(fallbackMethod, - annotatedMethod.getJavaMember().getParameterTypes()).getReturnType() - != annotatedMethod.getJavaMember().getReturnType()) { - throw new FaultToleranceDefinitionException("Return type of fallback method does not match."); - } - } catch (NoSuchMethodException ex) { - throw new FaultToleranceDefinitionException("Could not find fallback method: " + fallbackMethod, ex); - } - } else if (fallbackClass != null && fallbackClass != Fallback.DEFAULT.class) { - if (fallbackClass.getDeclaredMethod("handle", ExecutionContext.class).getReturnType() - != annotatedMethod.getJavaMember().getReturnType()) { - throw new FaultToleranceDefinitionException( - "Return type of fallback class handle method does not match."); - } - } - } -} diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/validators/RetryValidator.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/validators/RetryValidator.java deleted file mode 100644 index da7cc1380ba..00000000000 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/validators/RetryValidator.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. - * - * Copyright (c) [2017] Payara Foundation and/or its affiliates. All rights reserved. - * - * The contents of this file are subject to the terms of either the GNU - * General Public License Version 2 only ("GPL") or the Common Development - * and Distribution License("CDDL") (collectively, the "License"). You - * may not use this file except in compliance with the License. You can - * obtain a copy of the License at - * https://github.com/payara/Payara/blob/master/LICENSE.txt - * See the License for the specific - * language governing permissions and limitations under the License. - * - * When distributing the software, include this License Header Notice in each - * file and include the License file at glassfish/legal/LICENSE.txt. - * - * GPL Classpath Exception: - * The Payara Foundation designates this particular file as subject to the "Classpath" - * exception as provided by the Payara Foundation in the GPL Version 2 section of the License - * file that accompanied this code. - * - * Modifications: - * If applicable, add the following below the License Header, with the fields - * enclosed by brackets [] replaced by your own identifying information: - * "Portions Copyright [year] [name of copyright owner]" - * - * Contributor(s): - * If you wish your version of this file to be governed by only the CDDL or - * only the GPL Version 2, indicate your decision by adding "[Contributor] - * elects to include this software in this distribution under the [CDDL or GPL - * Version 2] license." If you don't indicate a single choice of license, a - * recipient has the option to distribute your version of this file under - * either the CDDL, the GPL Version 2 or to extend the choice of license to - * its licensees as provided above. However, if you add GPL Version 2 code - * and therefore, elected the GPL Version 2 license, then the option applies - * only if the new code is made subject to such option by the copyright - * holder. - */ -package fish.payara.microprofile.faulttolerance.validators; - -import javax.enterprise.inject.spi.AnnotatedMethod; -import org.eclipse.microprofile.config.Config; -import org.eclipse.microprofile.faulttolerance.Retry; -import org.eclipse.microprofile.faulttolerance.exceptions.FaultToleranceDefinitionException; - -import fish.payara.microprofile.faulttolerance.service.FaultToleranceUtils; - -/** - * Validator for the Fault Tolerance Retry annotation. - * @author Andrew Pielage - */ -public class RetryValidator { - - /** - * Validates the given Retry annotation. - * @param retry The annotation to validate - * @param annotatedMethod The annotated method to validate - * @param config The config to get any override values from - */ - public static void validateAnnotation(Retry retry, AnnotatedMethod annotatedMethod, Config config) { - int maxRetries = FaultToleranceUtils.getOverrideValue( - config, Retry.class, "maxRetries", annotatedMethod.getJavaMember().getName(), - annotatedMethod.getJavaMember().getDeclaringClass().getCanonicalName(), Integer.class) - .orElse(retry.maxRetries()); - - long delay = FaultToleranceUtils.getOverrideValue( - config, Retry.class, "delay", annotatedMethod.getJavaMember().getName(), - annotatedMethod.getJavaMember().getDeclaringClass().getCanonicalName(), Long.class) - .orElse(retry.delay()); - - long maxDuration = FaultToleranceUtils.getOverrideValue( - config, Retry.class, "maxDuration", annotatedMethod.getJavaMember().getName(), - annotatedMethod.getJavaMember().getDeclaringClass().getCanonicalName(), Long.class) - .orElse(retry.maxDuration()); - - long jitter = FaultToleranceUtils.getOverrideValue( - config, Retry.class, "jitter", annotatedMethod.getJavaMember().getName(), - annotatedMethod.getJavaMember().getDeclaringClass().getCanonicalName(), Long.class) - .orElse(retry.jitter()); - - if (maxRetries < -1) { - throw new FaultToleranceDefinitionException("Method \"" + annotatedMethod.getJavaMember().getName() + "\"" - + " annotated with " + Retry.class.getCanonicalName() - + " has a maxRetries value less than -1: " + maxRetries); - } - - if (delay < 0) { - throw new FaultToleranceDefinitionException("Method \"" + annotatedMethod.getJavaMember().getName() + "\"" - + " annotated with " + Retry.class.getCanonicalName() - + " has a delay value less than 0: " + delay); - } - - if (maxDuration < 0) { - throw new FaultToleranceDefinitionException("Method \"" + annotatedMethod.getJavaMember().getName() + "\"" - + " annotated with " + Retry.class.getCanonicalName() - + " has a maxDuration value less than 0: " + maxDuration); - } - - if (maxDuration <= delay) { - throw new FaultToleranceDefinitionException("Method \"" + annotatedMethod.getJavaMember().getName() + "\"" - + " annotated with " + Retry.class.getCanonicalName() - + " has a maxDuration value less than or equal to the delay value: " + maxDuration); - } - - if (jitter < 0) { - throw new FaultToleranceDefinitionException("Method \"" + annotatedMethod.getJavaMember().getName() + "\"" - + " annotated with " + Retry.class.getCanonicalName() - + " has a jitter value less than 0: " + jitter); - } - } -} diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/validators/TimeoutValidator.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/validators/TimeoutValidator.java deleted file mode 100644 index 187e2f4934a..00000000000 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/validators/TimeoutValidator.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. - * - * Copyright (c) [2017] Payara Foundation and/or its affiliates. All rights reserved. - * - * The contents of this file are subject to the terms of either the GNU - * General Public License Version 2 only ("GPL") or the Common Development - * and Distribution License("CDDL") (collectively, the "License"). You - * may not use this file except in compliance with the License. You can - * obtain a copy of the License at - * https://github.com/payara/Payara/blob/master/LICENSE.txt - * See the License for the specific - * language governing permissions and limitations under the License. - * - * When distributing the software, include this License Header Notice in each - * file and include the License file at glassfish/legal/LICENSE.txt. - * - * GPL Classpath Exception: - * The Payara Foundation designates this particular file as subject to the "Classpath" - * exception as provided by the Payara Foundation in the GPL Version 2 section of the License - * file that accompanied this code. - * - * Modifications: - * If applicable, add the following below the License Header, with the fields - * enclosed by brackets [] replaced by your own identifying information: - * "Portions Copyright [year] [name of copyright owner]" - * - * Contributor(s): - * If you wish your version of this file to be governed by only the CDDL or - * only the GPL Version 2, indicate your decision by adding "[Contributor] - * elects to include this software in this distribution under the [CDDL or GPL - * Version 2] license." If you don't indicate a single choice of license, a - * recipient has the option to distribute your version of this file under - * either the CDDL, the GPL Version 2 or to extend the choice of license to - * its licensees as provided above. However, if you add GPL Version 2 code - * and therefore, elected the GPL Version 2 license, then the option applies - * only if the new code is made subject to such option by the copyright - * holder. - */ -package fish.payara.microprofile.faulttolerance.validators; - -import javax.enterprise.inject.spi.AnnotatedMethod; -import org.eclipse.microprofile.config.Config; -import org.eclipse.microprofile.faulttolerance.Timeout; -import org.eclipse.microprofile.faulttolerance.exceptions.FaultToleranceDefinitionException; - -import fish.payara.microprofile.faulttolerance.service.FaultToleranceUtils; - -/** - * Validator for the Fault Tolerance Timeout annotation. - * @author Andrew Pielage - */ -public class TimeoutValidator { - public static void validateAnnotation(Timeout timeout, AnnotatedMethod annotatedMethod, Config config) { - long value = FaultToleranceUtils.getOverrideValue( - config, Timeout.class, "value", annotatedMethod.getJavaMember().getName(), - annotatedMethod.getJavaMember().getDeclaringClass().getCanonicalName(), Long.class) - .orElse(timeout.value()); - - if (value < 0) { - throw new FaultToleranceDefinitionException("Method \"" + annotatedMethod.getJavaMember().getName() + "\"" - + " annotated with " + Timeout.class.getCanonicalName() - + " has a value less than 0: " + value); - } - } -} From cf64a57cb6e757e0a503e8bcfb0ddf3769b0d4cf Mon Sep 17 00:00:00 2001 From: Jan Bernitt Date: Fri, 19 Apr 2019 21:17:02 +0200 Subject: [PATCH 13/30] PAYARA-3468 interceptor priority via config; cleanup and rename --- .../faulttolerance/FaultToleranceConfig.java | 8 --- .../FaultTolerance.java} | 4 +- ...sion.java => FaultToleranceExtension.java} | 50 ++++++++++++++----- .../FaultToleranceInterceptor.java | 11 ++-- ...java => BindableFaultToleranceConfig.java} | 26 +++------- ...ava => BindableFaultToleranceMetrics.java} | 15 +++--- .../FaultToleranceApplicationState.java | 8 +-- .../service/FaultToleranceServiceImpl.java | 4 +- .../javax.enterprise.inject.spi.Extension | 2 +- 9 files changed, 62 insertions(+), 66 deletions(-) rename appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/{interceptors/FaultToleranceBehaviour.java => cdi/FaultTolerance.java} (89%) rename appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/cdi/{FaultToleranceCDIExtension.java => FaultToleranceExtension.java} (77%) rename appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/{interceptors => cdi}/FaultToleranceInterceptor.java (87%) rename appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/service/{FaultToleranceConfigFactory.java => BindableFaultToleranceConfig.java} (89%) rename appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/service/{FaultToleranceMetricsFactory.java => BindableFaultToleranceMetrics.java} (79%) diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/FaultToleranceConfig.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/FaultToleranceConfig.java index 94ca2249de0..3ce5ffff692 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/FaultToleranceConfig.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/FaultToleranceConfig.java @@ -4,8 +4,6 @@ import java.lang.reflect.Method; import java.time.temporal.ChronoUnit; -import javax.interceptor.Interceptor; - import org.eclipse.microprofile.faulttolerance.Bulkhead; import org.eclipse.microprofile.faulttolerance.CircuitBreaker; import org.eclipse.microprofile.faulttolerance.Fallback; @@ -24,8 +22,6 @@ @FunctionalInterface public interface FaultToleranceConfig { - int DEFAULT_INTERCEPTOR_PRIORITY = Interceptor.Priority.PLATFORM_AFTER + 15; - /** * FT behaves as stated by the present FT annotations. */ @@ -62,10 +58,6 @@ default boolean isAnnotationPresent(Class annotationType) return getAnnotation(annotationType) != null; } - default int interceptorPriority() { - return DEFAULT_INTERCEPTOR_PRIORITY; - } - /* * @Retry diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/FaultToleranceBehaviour.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/cdi/FaultTolerance.java similarity index 89% rename from appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/FaultToleranceBehaviour.java rename to appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/cdi/FaultTolerance.java index 6591f5d86a2..58b66864b88 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/FaultToleranceBehaviour.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/cdi/FaultTolerance.java @@ -1,4 +1,4 @@ -package fish.payara.microprofile.faulttolerance.interceptors; +package fish.payara.microprofile.faulttolerance.cdi; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; @@ -27,6 +27,6 @@ @InterceptorBinding @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.METHOD, ElementType.TYPE }) -public @interface FaultToleranceBehaviour { +public @interface FaultTolerance { //marker } diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/cdi/FaultToleranceCDIExtension.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/cdi/FaultToleranceExtension.java similarity index 77% rename from appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/cdi/FaultToleranceCDIExtension.java rename to appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/cdi/FaultToleranceExtension.java index 2605e8df138..a95f5aa9e40 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/cdi/FaultToleranceCDIExtension.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/cdi/FaultToleranceExtension.java @@ -39,14 +39,14 @@ */ package fish.payara.microprofile.faulttolerance.cdi; -import fish.payara.microprofile.faulttolerance.interceptors.FaultToleranceBehaviour; -import fish.payara.microprofile.faulttolerance.interceptors.FaultToleranceInterceptor; import fish.payara.microprofile.faulttolerance.policy.FaultTolerancePolicy; import fish.payara.microprofile.faulttolerance.service.FaultToleranceUtils; import java.lang.annotation.Annotation; import java.lang.reflect.Method; +import java.util.Optional; +import javax.annotation.Priority; import javax.enterprise.event.Observes; import javax.enterprise.inject.spi.BeanManager; import javax.enterprise.inject.spi.BeforeBeanDiscovery; @@ -54,7 +54,9 @@ import javax.enterprise.inject.spi.ProcessAnnotatedType; import javax.enterprise.inject.spi.WithAnnotations; import javax.enterprise.inject.spi.configurator.AnnotatedMethodConfigurator; +import javax.enterprise.util.AnnotationLiteral; +import org.eclipse.microprofile.config.ConfigProvider; import org.eclipse.microprofile.faulttolerance.Asynchronous; import org.eclipse.microprofile.faulttolerance.Bulkhead; import org.eclipse.microprofile.faulttolerance.CircuitBreaker; @@ -68,31 +70,28 @@ * @author Andrew Pielage * @author Jan Bernitt (2.0) */ -public class FaultToleranceCDIExtension implements Extension { +public class FaultToleranceExtension implements Extension { /** - * The {@link FaultToleranceBehaviour} "instance" we use to dynamically mark methods at runtime that should be + * The {@link FaultTolerance} "instance" we use to dynamically mark methods at runtime that should be * handled by the {@link FaultToleranceInterceptor} that handles all of the FT annotations. */ - private static final Annotation MARKER = () -> FaultToleranceBehaviour.class; + private static final Annotation MARKER = () -> FaultTolerance.class; + + private static final String INTERCEPTOR_PRIORITY_PROPERTY = "mp.fault.tolerance.interceptor.priority"; void beforeBeanDiscovery(@Observes BeforeBeanDiscovery beforeBeanDiscovery, BeanManager beanManager) { beforeBeanDiscovery.addAnnotatedType(beanManager.createAnnotatedType(FaultToleranceInterceptor.class), "MP-FT"); } - void processAnnotatedType(@Observes @WithAnnotations({ Asynchronous.class, Bulkhead.class, CircuitBreaker.class, - Fallback.class, Retry.class, Timeout.class }) ProcessAnnotatedType processAnnotatedType, - BeanManager beanManager) throws Exception { - validateAndMark(processAnnotatedType); - } - /** - * Marks all {@link Method}s *affected* by FT annotation with the {@link FaultToleranceBehaviour} annotation which + * Marks all {@link Method}s *affected* by FT annotation with the {@link FaultTolerance} annotation which * is handled by the {@link FaultToleranceInterceptor} which processes the FT annotations. * * @param processAnnotatedType type currently processed */ - private static void validateAndMark(ProcessAnnotatedType processAnnotatedType) { + void processAnnotatedType(@Observes @WithAnnotations({ Asynchronous.class, Bulkhead.class, CircuitBreaker.class, + Fallback.class, Retry.class, Timeout.class }) ProcessAnnotatedType processAnnotatedType) throws Exception { boolean markAllMethods = FaultToleranceUtils .isAnnotaetdWithFaultToleranceAnnotations(processAnnotatedType.getAnnotatedType()); Class targetClass = processAnnotatedType.getAnnotatedType().getJavaClass(); @@ -104,4 +103,29 @@ private static void validateAndMark(ProcessAnnotatedType processAnnotated } } } + + void changeInterceptorPriority(@Observes ProcessAnnotatedType interceptorType) { + Optional priorityOverride = ConfigProvider.getConfig().getOptionalValue(INTERCEPTOR_PRIORITY_PROPERTY, + Integer.class); + if (priorityOverride.isPresent()) { + interceptorType.configureAnnotatedType() + .remove(annotation -> annotation instanceof Priority) + .add(new PriorityLiteral(priorityOverride.get())); + } + } + + public static final class PriorityLiteral extends AnnotationLiteral implements Priority { + private static final long serialVersionUID = 1L; + + private final int value; + + public PriorityLiteral(int value) { + this.value = value; + } + + @Override + public int value() { + return value; + } + } } diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/FaultToleranceInterceptor.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/cdi/FaultToleranceInterceptor.java similarity index 87% rename from appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/FaultToleranceInterceptor.java rename to appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/cdi/FaultToleranceInterceptor.java index d6d7f07a78e..87a7a50457c 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/interceptors/FaultToleranceInterceptor.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/cdi/FaultToleranceInterceptor.java @@ -1,4 +1,4 @@ -package fish.payara.microprofile.faulttolerance.interceptors; +package fish.payara.microprofile.faulttolerance.cdi; import java.io.Serializable; import java.lang.annotation.Annotation; @@ -8,7 +8,6 @@ import javax.annotation.Priority; import javax.enterprise.inject.spi.BeanManager; -import javax.enterprise.inject.spi.Prioritized; import javax.inject.Inject; import javax.interceptor.AroundInvoke; import javax.interceptor.Interceptor; @@ -22,9 +21,9 @@ import fish.payara.microprofile.faulttolerance.service.Stereotypes; @Interceptor -@FaultToleranceBehaviour +@FaultTolerance @Priority(Interceptor.Priority.PLATFORM_AFTER + 15) -public class FaultToleranceInterceptor implements Stereotypes, Serializable, Prioritized { +public class FaultToleranceInterceptor implements Stereotypes, Serializable { private static final Logger logger = Logger.getLogger(FaultToleranceInterceptor.class.getName()); @@ -58,9 +57,5 @@ public Set getStereotypeDefinition(Class stere return beanManager.getStereotypeDefinition(stereotype); } - @Override - public int getPriority() { - return Interceptor.Priority.PLATFORM_AFTER + 15; //TODO dynamic via config - } } diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/service/FaultToleranceConfigFactory.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/service/BindableFaultToleranceConfig.java similarity index 89% rename from appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/service/FaultToleranceConfigFactory.java rename to appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/service/BindableFaultToleranceConfig.java index 62aaabc20b9..cfb14def2d1 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/service/FaultToleranceConfigFactory.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/service/BindableFaultToleranceConfig.java @@ -4,7 +4,6 @@ import java.time.temporal.ChronoUnit; import java.util.ArrayList; import java.util.List; -import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import java.util.logging.Level; import java.util.logging.Logger; @@ -28,13 +27,12 @@ * * @author Jan Bernitt */ -final class FaultToleranceConfigFactory implements FaultToleranceConfig { +final class BindableFaultToleranceConfig implements FaultToleranceConfig { private static final String NON_FALLBACK_ENABLED_PROPERTY = "MP_Fault_Tolerance_NonFallback_Enabled"; private static final String METRICS_ENABLED_PROPERTY = "MP_Fault_Tolerance_Metrics_Enabled"; - private static final String INTERCEPTOR_PRIORITY_PROPERTY = "mp.fault.tolerance.interceptor.priority"; - private static final Logger logger = Logger.getLogger(FaultToleranceConfigFactory.class.getName()); + private static final Logger logger = Logger.getLogger(BindableFaultToleranceConfig.class.getName()); /** * These tree properties should only be read once at the start of the application, therefore they are cached in @@ -42,29 +40,25 @@ final class FaultToleranceConfigFactory implements FaultToleranceConfig { */ private final AtomicReference nonFallbackEnabled; private final AtomicReference metricsEnabled; - private final AtomicInteger interceptorPriority; private final Config config; private final Stereotypes sterotypes; private final InvocationContext context; - public FaultToleranceConfigFactory(Stereotypes sterotypes) { + public BindableFaultToleranceConfig(Stereotypes sterotypes) { this.sterotypes = sterotypes; this.config = resolveConfig(); this.nonFallbackEnabled = new AtomicReference<>(); this.metricsEnabled = new AtomicReference<>(); - this.interceptorPriority = new AtomicInteger(-1); this.context = null; // factory is unbound } - private FaultToleranceConfigFactory(InvocationContext context, Stereotypes sterotypes, Config config, - AtomicReference nonFallbackEnabled, AtomicReference metricsEnabled, - AtomicInteger interceptorPriority) { + private BindableFaultToleranceConfig(InvocationContext context, Stereotypes sterotypes, Config config, + AtomicReference nonFallbackEnabled, AtomicReference metricsEnabled) { this.context = context; this.sterotypes = sterotypes; this.config = config; this.nonFallbackEnabled = nonFallbackEnabled; this.metricsEnabled = metricsEnabled; - this.interceptorPriority = interceptorPriority; } private static Config resolveConfig() { @@ -84,8 +78,7 @@ private static Config resolveConfig() { public FaultToleranceConfig bindTo(InvocationContext context) { return config == null ? FaultToleranceConfig.asAnnotated(context.getTarget().getClass(), context.getMethod()) - : new FaultToleranceConfigFactory(context, sterotypes, config, nonFallbackEnabled, metricsEnabled, - interceptorPriority); + : new BindableFaultToleranceConfig(context, sterotypes, config, nonFallbackEnabled, metricsEnabled); } /* @@ -121,13 +114,6 @@ public boolean isEnabled(Class annotationType) { annotationType == Fallback.class || isNonFallbackEnabled()); } - @Override - public int interceptorPriority() { - return interceptorPriority.updateAndGet(priority -> priority > 0 ? priority - : config.getOptionalValue(INTERCEPTOR_PRIORITY_PROPERTY, Integer.class) - .orElse(DEFAULT_INTERCEPTOR_PRIORITY)); - } - /* * Retry */ diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/service/FaultToleranceMetricsFactory.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/service/BindableFaultToleranceMetrics.java similarity index 79% rename from appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/service/FaultToleranceMetricsFactory.java rename to appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/service/BindableFaultToleranceMetrics.java index 43382b6f96e..e0c1bd5f0fe 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/service/FaultToleranceMetricsFactory.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/service/BindableFaultToleranceMetrics.java @@ -13,18 +13,17 @@ import fish.payara.microprofile.faulttolerance.FaultToleranceMetrics; /** - * The {@link FaultToleranceMetricsFactory} works both as a factory where {@link #bindTo(InvocationContext)} is used to - * create context aware instances of a {@link FaultToleranceMetrics} and as the bound {@link FaultToleranceMetrics}. For - * thread safety immutable objects are used or created. + * The {@link BindableFaultToleranceMetrics} works both as a factory where {@link #bindTo(InvocationContext)} is used to + * create context aware instances of a {@link FaultToleranceMetrics}. * * This {@link FaultToleranceMetrics} uses {@link CDI} to resolve the {@link MetricRegistry}. When resolution fails * {@link #bindTo(InvocationContext)} will use {@link FaultToleranceMetrics#DISABLED}. * * @author Jan Bernitt */ -final class FaultToleranceMetricsFactory implements FaultToleranceMetrics { +final class BindableFaultToleranceMetrics implements FaultToleranceMetrics { - private static final Logger logger = Logger.getLogger(FaultToleranceMetricsFactory.class.getName()); + private static final Logger logger = Logger.getLogger(BindableFaultToleranceMetrics.class.getName()); private final MetricRegistry metricRegistry; /** @@ -33,12 +32,12 @@ final class FaultToleranceMetricsFactory implements FaultToleranceMetrics { */ private final String canonicalMethodName; - public FaultToleranceMetricsFactory() { + public BindableFaultToleranceMetrics() { this.metricRegistry = resolveRegistry(); this.canonicalMethodName = "(unbound)"; } - private FaultToleranceMetricsFactory(MetricRegistry metricRegistry, String canonicalMethodName) { + private BindableFaultToleranceMetrics(MetricRegistry metricRegistry, String canonicalMethodName) { this.metricRegistry = metricRegistry; this.canonicalMethodName = canonicalMethodName; } @@ -60,7 +59,7 @@ private static MetricRegistry resolveRegistry() { FaultToleranceMetrics bindTo(InvocationContext context) { return metricRegistry == null ? FaultToleranceMetrics.DISABLED - : new FaultToleranceMetricsFactory(metricRegistry, FaultToleranceUtils.getCanonicalMethodName(context)); + : new BindableFaultToleranceMetrics(metricRegistry, FaultToleranceUtils.getCanonicalMethodName(context)); } /* diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/service/FaultToleranceApplicationState.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/service/FaultToleranceApplicationState.java index 698221a43ff..875e90dbbdb 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/service/FaultToleranceApplicationState.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/service/FaultToleranceApplicationState.java @@ -53,8 +53,8 @@ final class FaultToleranceApplicationState { private final Map> circuitBreakerStates = new ConcurrentHashMap<>(); private final Map> bulkheadExecutionSemaphores = new ConcurrentHashMap<>(); private final Map> bulkheadExecutionQueueSemaphores = new ConcurrentHashMap<>(); - private final AtomicReference config = new AtomicReference<>(); - private final AtomicReference metrics = new AtomicReference<>(); + private final AtomicReference config = new AtomicReference<>(); + private final AtomicReference metrics = new AtomicReference<>(); public Map> getCircuitBreakerStates() { return circuitBreakerStates; @@ -68,11 +68,11 @@ public Map> getBulkheadExecutionQueueSema return bulkheadExecutionQueueSemaphores; } - public AtomicReference getConfig() { + public AtomicReference getConfig() { return config; } - public AtomicReference getMetrics() { + public AtomicReference getMetrics() { return metrics; } } diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/service/FaultToleranceServiceImpl.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/service/FaultToleranceServiceImpl.java index 67f582a4d51..de2a58ad0f1 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/service/FaultToleranceServiceImpl.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/service/FaultToleranceServiceImpl.java @@ -145,14 +145,14 @@ public void event(Event event) { public FaultToleranceConfig getConfig(InvocationContext context, Stereotypes stereotypes) { FaultToleranceApplicationState appState = getApplicationState(getApplicationContext(context)); return appState.getConfig().updateAndGet( - config -> config != null ? config : new FaultToleranceConfigFactory(stereotypes)).bindTo(context); + config -> config != null ? config : new BindableFaultToleranceConfig(stereotypes)).bindTo(context); } @Override public FaultToleranceMetrics getMetrics(InvocationContext context) { FaultToleranceApplicationState appState = getApplicationState(getApplicationContext(context)); return appState.getMetrics().updateAndGet( - metrics -> metrics != null ? metrics : new FaultToleranceMetricsFactory()).bindTo(context); + metrics -> metrics != null ? metrics : new BindableFaultToleranceMetrics()).bindTo(context); } //TODO use the scheduler to schedule a clean of FT Info diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension index 9b0d9ce9b6e..b8bc8754c65 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension @@ -1 +1 @@ -fish.payara.microprofile.faulttolerance.cdi.FaultToleranceCDIExtension \ No newline at end of file +fish.payara.microprofile.faulttolerance.cdi.FaultToleranceExtension \ No newline at end of file From d3bf29bb6c00e57fe9f5d704370889a98135f29f Mon Sep 17 00:00:00 2001 From: Jan Bernitt Date: Mon, 22 Apr 2019 10:31:13 +0200 Subject: [PATCH 14/30] PAYARA-3468 added async tracing, javadoc and copyright headers --- .../faulttolerance/FaultToleranceConfig.java | 72 +++++++++++++++- .../faulttolerance/FaultToleranceMetrics.java | 43 ++++++++++ .../faulttolerance/FaultToleranceService.java | 86 ++++++++++++++++++- .../faulttolerance/cdi/FaultTolerance.java | 39 +++++++++ .../cdi/FaultToleranceExtension.java | 2 +- .../cdi/FaultToleranceInterceptor.java | 39 +++++++++ .../policy/AsynchronousPolicy.java | 39 +++++++++ .../faulttolerance/policy/BulkheadPolicy.java | 39 +++++++++ .../policy/CircuitBreakerPolicy.java | 39 +++++++++ .../faulttolerance/policy/FallbackPolicy.java | 39 +++++++++ .../policy/FaultTolerancePolicy.java | 43 +++++++++- .../policy/MethodLookupUtils.java | 39 +++++++++ .../faulttolerance/policy/Policy.java | 39 +++++++++ .../faulttolerance/policy/RetryPolicy.java | 39 +++++++++ .../policy/StaticAnalysisContext.java | 39 +++++++++ .../faulttolerance/policy/TimeoutPolicy.java | 39 +++++++++ .../service/BindableFaultToleranceConfig.java | 39 +++++++++ .../BindableFaultToleranceMetrics.java | 39 +++++++++ .../FaultToleranceApplicationState.java | 2 +- .../FaultToleranceExecutionContext.java | 39 +++++++++ .../service/FaultToleranceServiceImpl.java | 11 ++- .../service/FaultToleranceUtils.java | 2 +- .../faulttolerance/service/Stereotypes.java | 51 +++++++++++ 23 files changed, 845 insertions(+), 13 deletions(-) diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/FaultToleranceConfig.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/FaultToleranceConfig.java index 3ce5ffff692..865f01492a1 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/FaultToleranceConfig.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/FaultToleranceConfig.java @@ -1,3 +1,42 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright (c) 2019 Payara Foundation and/or its affiliates. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common Development + * and Distribution License("CDDL") (collectively, the "License"). You + * may not use this file except in compliance with the License. You can + * obtain a copy of the License at + * https://github.com/payara/Payara/blob/master/LICENSE.txt + * See the License for the specific + * language governing permissions and limitations under the License. + * + * When distributing the software, include this License Header Notice in each + * file and include the License file at glassfish/legal/LICENSE.txt. + * + * GPL Classpath Exception: + * The Payara Foundation designates this particular file as subject to the "Classpath" + * exception as provided by the Payara Foundation in the GPL Version 2 section of the License + * file that accompanied this code. + * + * Modifications: + * If applicable, add the following below the License Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyright [year] [name of copyright owner]" + * + * Contributor(s): + * If you wish your version of this file to be governed by only the CDDL or + * only the GPL Version 2, indicate your decision by adding "[Contributor] + * elects to include this software in this distribution under the [CDDL or GPL + * Version 2] license." If you don't indicate a single choice of license, a + * recipient has the option to distribute your version of this file under + * either the CDDL, the GPL Version 2 or to extend the choice of license to + * its licensees as provided above. However, if you add GPL Version 2 code + * and therefore, elected the GPL Version 2 license, then the option applies + * only if the new code is made subject to such option by the copyright + * holder. + */ package fish.payara.microprofile.faulttolerance; import java.lang.annotation.Annotation; @@ -15,7 +54,8 @@ * Encapsulates all properties extracted from FT annotations and the {@link org.eclipse.microprofile.config.Config} so * that the processing can be declared independent of the actual resolution mechanism. * - * The default implementations provided will extract properties plain from the given annotations. + * A configuration is bound to a specific invocation context which is not an argument to each of the provided methods + * but passed to the implementation upon construction. For another invocation another configuration instance is bound. * * @author Jan Bernitt */ @@ -23,7 +63,8 @@ public interface FaultToleranceConfig { /** - * FT behaves as stated by the present FT annotations. + * @return A {@link FaultToleranceConfig} that behaves as stated by the present FT annotations. {@link Method} + * annotations take precedence over the {@link Class} level annotations. */ static FaultToleranceConfig asAnnotated(Class target, Method method) { return new FaultToleranceConfig() { @@ -35,25 +76,50 @@ public A getAnnotation(Class annotationType) { }; } + /** + * Returns the value of the given annotation type for the invocation context was bound to upon construction. + * + * @param annotationType type to lookup + * @return the annotation of the given type if present or {@code null} otherwise + */ A getAnnotation(Class annotationType); /* * General */ + /** + * Check global generic annotation switch. + * + * @return true if (in addition to {@link Fallback}, which is always enabled) the other FT annotations are as well + * (default). Mostly used to disable these which will not disable {@link Fallback}. + */ default boolean isNonFallbackEnabled() { return true; } - @SuppressWarnings("unused") + /** + * Check global annotation specific annotation switch. + * + * @param annotationType the annotation type to check + * @return true if the given annotation type is globally enabled, false if it is globally disabled + */ default boolean isEnabled(Class annotationType) { return true; } + /** + * Check for global metrics switch. + * + * @return true if metrics are enabled, false otherwise. + */ default boolean isMetricsEnabled() { return true; } + /** + * @see #getAnnotation(Class) + */ default boolean isAnnotationPresent(Class annotationType) { return getAnnotation(annotationType) != null; } diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/FaultToleranceMetrics.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/FaultToleranceMetrics.java index 57dd9cb23b8..6f3c5c81d9e 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/FaultToleranceMetrics.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/FaultToleranceMetrics.java @@ -1,3 +1,42 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright (c) 2019 Payara Foundation and/or its affiliates. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common Development + * and Distribution License("CDDL") (collectively, the "License"). You + * may not use this file except in compliance with the License. You can + * obtain a copy of the License at + * https://github.com/payara/Payara/blob/master/LICENSE.txt + * See the License for the specific + * language governing permissions and limitations under the License. + * + * When distributing the software, include this License Header Notice in each + * file and include the License file at glassfish/legal/LICENSE.txt. + * + * GPL Classpath Exception: + * The Payara Foundation designates this particular file as subject to the "Classpath" + * exception as provided by the Payara Foundation in the GPL Version 2 section of the License + * file that accompanied this code. + * + * Modifications: + * If applicable, add the following below the License Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyright [year] [name of copyright owner]" + * + * Contributor(s): + * If you wish your version of this file to be governed by only the CDDL or + * only the GPL Version 2, indicate your decision by adding "[Contributor] + * elects to include this software in this distribution under the [CDDL or GPL + * Version 2] license." If you don't indicate a single choice of license, a + * recipient has the option to distribute your version of this file under + * either the CDDL, the GPL Version 2 or to extend the choice of license to + * its licensees as provided above. However, if you add GPL Version 2 code + * and therefore, elected the GPL Version 2 license, then the option applies + * only if the new code is made subject to such option by the copyright + * holder. + */ package fish.payara.microprofile.faulttolerance; import java.util.function.LongSupplier; @@ -5,6 +44,9 @@ /** * Encodes the specifics of the FT metrics names using default methods while decoupling rest of the implementation from * the {@link org.eclipse.microprofile.metrics.MetricRegistry}. + * + * The metrics are bound to a specific invocation context which is not an argument to each of the provided methods but + * passed to the implementation upon construction. For another invocation another metrics instance is bound. * * @author Jan Bernitt */ @@ -15,6 +57,7 @@ public interface FaultToleranceMetrics { */ FaultToleranceMetrics DISABLED = new FaultToleranceMetrics() { /* does nothing */ }; + /* * Generic (to be implemented/overridden) */ diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/FaultToleranceService.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/FaultToleranceService.java index d753d43477a..36867e68e8d 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/FaultToleranceService.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/FaultToleranceService.java @@ -1,3 +1,42 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright (c) 2019 Payara Foundation and/or its affiliates. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common Development + * and Distribution License("CDDL") (collectively, the "License"). You + * may not use this file except in compliance with the License. You can + * obtain a copy of the License at + * https://github.com/payara/Payara/blob/master/LICENSE.txt + * See the License for the specific + * language governing permissions and limitations under the License. + * + * When distributing the software, include this License Header Notice in each + * file and include the License file at glassfish/legal/LICENSE.txt. + * + * GPL Classpath Exception: + * The Payara Foundation designates this particular file as subject to the "Classpath" + * exception as provided by the Payara Foundation in the GPL Version 2 section of the License + * file that accompanied this code. + * + * Modifications: + * If applicable, add the following below the License Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyright [year] [name of copyright owner]" + * + * Contributor(s): + * If you wish your version of this file to be governed by only the CDDL or + * only the GPL Version 2, indicate your decision by adding "[Contributor] + * elects to include this software in this distribution under the [CDDL or GPL + * Version 2] license." If you don't indicate a single choice of license, a + * recipient has the option to distribute your version of this file under + * either the CDDL, the GPL Version 2 or to extend the choice of license to + * its licensees as provided above. However, if you add GPL Version 2 code + * and therefore, elected the GPL Version 2 license, then the option applies + * only if the new code is made subject to such option by the copyright + * holder. + */ package fish.payara.microprofile.faulttolerance; import java.lang.reflect.Method; @@ -28,10 +67,25 @@ public interface FaultToleranceService { * Factory methods */ + /** + * Creates an instance of a {@link FaultToleranceConfig} bound to the given {@link InvocationContext} and + * {@link Stereotypes} lookup. + * + * @param context currently processed context + * @param stereotypes way to lookup sterotype annotations + * @return a thread safe {@link FaultToleranceConfig} instance bound to the given context + */ FaultToleranceConfig getConfig(InvocationContext context, Stereotypes stereotypes); + /** + * Creates an instance of {@link FaultToleranceMetrics} bound to the given {@link InvocationContext}. + * + * @param context currently processed context + * @return a thread safe {@link FaultToleranceMetrics} instance bound to the given context + */ FaultToleranceMetrics getMetrics(InvocationContext context); + /* * State */ @@ -42,6 +96,7 @@ public interface FaultToleranceService { BulkheadSemaphore getWaitingQueuePopulation(int queueCapacity, InvocationContext context); + /* * Processing */ @@ -69,24 +124,53 @@ public interface FaultToleranceService { * * @param asyncResult a not yet completed {@link CompletableFuture} that should receive the result of the operation * when it is executed + * @param context the currently processed context (for e.g. tracing) * @param task an operation that must compute a value of type {@link Future} or {@link CompletionStage}. * @throws RejectedExecutionException In case the task could not be accepted for execution. Usually due to too many * work in progress. */ - void runAsynchronous(CompletableFuture asyncResult, Callable task) + void runAsynchronous(CompletableFuture asyncResult, InvocationContext context, Callable task) throws RejectedExecutionException; + /** + * Invokes the instance of the given {@link FallbackHandler} {@link Class} defined in the given context to handle + * the given {@link Exception}. + * + * @param fallbackClass the type of {@link FallbackHandler} to resolve or instantiate and use + * @param context the currently processed context to use for arguments + * @param ex the {@link Exception} thrown by the FT processing to handle by the {@link FallbackHandler} + * @return the result returned by the invoked {@link FallbackHandler} + * @throws Exception in case resolving, instantiating or invoking the handler method fails + */ Object fallbackHandle(Class> fallbackClass, InvocationContext context, Exception ex) throws Exception; + /** + * Invokes the given fallback {@link Method} in the given context. + * + * @param fallbackMethod the {@link Method} to invoke + * @param context the currently processed context to use for target instance and method arguments + * @return the result returned by the invoked fallback method + * @throws Exception in case invoking the method fails or the invoked method threw an {@link Exception} + */ Object fallbackInvoke(Method fallbackMethod, InvocationContext context) throws Exception; + /* * Tracing */ + /** + * Starts tracing the given context named with the given method label. + * + * @param method the label to use for the trace + * @param context the currently processed context + */ void trace(String method, InvocationContext context); + /** + * Ends the innermost trace. + */ void endTrace(); } diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/cdi/FaultTolerance.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/cdi/FaultTolerance.java index 58b66864b88..9cd2597e9eb 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/cdi/FaultTolerance.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/cdi/FaultTolerance.java @@ -1,3 +1,42 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright (c) 2019 Payara Foundation and/or its affiliates. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common Development + * and Distribution License("CDDL") (collectively, the "License"). You + * may not use this file except in compliance with the License. You can + * obtain a copy of the License at + * https://github.com/payara/Payara/blob/master/LICENSE.txt + * See the License for the specific + * language governing permissions and limitations under the License. + * + * When distributing the software, include this License Header Notice in each + * file and include the License file at glassfish/legal/LICENSE.txt. + * + * GPL Classpath Exception: + * The Payara Foundation designates this particular file as subject to the "Classpath" + * exception as provided by the Payara Foundation in the GPL Version 2 section of the License + * file that accompanied this code. + * + * Modifications: + * If applicable, add the following below the License Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyright [year] [name of copyright owner]" + * + * Contributor(s): + * If you wish your version of this file to be governed by only the CDDL or + * only the GPL Version 2, indicate your decision by adding "[Contributor] + * elects to include this software in this distribution under the [CDDL or GPL + * Version 2] license." If you don't indicate a single choice of license, a + * recipient has the option to distribute your version of this file under + * either the CDDL, the GPL Version 2 or to extend the choice of license to + * its licensees as provided above. However, if you add GPL Version 2 code + * and therefore, elected the GPL Version 2 license, then the option applies + * only if the new code is made subject to such option by the copyright + * holder. + */ package fish.payara.microprofile.faulttolerance.cdi; import java.lang.annotation.ElementType; diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/cdi/FaultToleranceExtension.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/cdi/FaultToleranceExtension.java index a95f5aa9e40..aae1f883b4a 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/cdi/FaultToleranceExtension.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/cdi/FaultToleranceExtension.java @@ -1,7 +1,7 @@ /* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * - * Copyright (c) [2017] Payara Foundation and/or its affiliates. All rights reserved. + * Copyright (c) [2017-2019] Payara Foundation and/or its affiliates. All rights reserved. * * The contents of this file are subject to the terms of either the GNU * General Public License Version 2 only ("GPL") or the Common Development diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/cdi/FaultToleranceInterceptor.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/cdi/FaultToleranceInterceptor.java index 87a7a50457c..c2c81b3a58b 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/cdi/FaultToleranceInterceptor.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/cdi/FaultToleranceInterceptor.java @@ -1,3 +1,42 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright (c) 2019 Payara Foundation and/or its affiliates. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common Development + * and Distribution License("CDDL") (collectively, the "License"). You + * may not use this file except in compliance with the License. You can + * obtain a copy of the License at + * https://github.com/payara/Payara/blob/master/LICENSE.txt + * See the License for the specific + * language governing permissions and limitations under the License. + * + * When distributing the software, include this License Header Notice in each + * file and include the License file at glassfish/legal/LICENSE.txt. + * + * GPL Classpath Exception: + * The Payara Foundation designates this particular file as subject to the "Classpath" + * exception as provided by the Payara Foundation in the GPL Version 2 section of the License + * file that accompanied this code. + * + * Modifications: + * If applicable, add the following below the License Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyright [year] [name of copyright owner]" + * + * Contributor(s): + * If you wish your version of this file to be governed by only the CDDL or + * only the GPL Version 2, indicate your decision by adding "[Contributor] + * elects to include this software in this distribution under the [CDDL or GPL + * Version 2] license." If you don't indicate a single choice of license, a + * recipient has the option to distribute your version of this file under + * either the CDDL, the GPL Version 2 or to extend the choice of license to + * its licensees as provided above. However, if you add GPL Version 2 code + * and therefore, elected the GPL Version 2 license, then the option applies + * only if the new code is made subject to such option by the copyright + * holder. + */ package fish.payara.microprofile.faulttolerance.cdi; import java.io.Serializable; diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/AsynchronousPolicy.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/AsynchronousPolicy.java index 9f3ddccf7ab..22e3d4144ed 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/AsynchronousPolicy.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/AsynchronousPolicy.java @@ -1,3 +1,42 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright (c) 2019 Payara Foundation and/or its affiliates. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common Development + * and Distribution License("CDDL") (collectively, the "License"). You + * may not use this file except in compliance with the License. You can + * obtain a copy of the License at + * https://github.com/payara/Payara/blob/master/LICENSE.txt + * See the License for the specific + * language governing permissions and limitations under the License. + * + * When distributing the software, include this License Header Notice in each + * file and include the License file at glassfish/legal/LICENSE.txt. + * + * GPL Classpath Exception: + * The Payara Foundation designates this particular file as subject to the "Classpath" + * exception as provided by the Payara Foundation in the GPL Version 2 section of the License + * file that accompanied this code. + * + * Modifications: + * If applicable, add the following below the License Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyright [year] [name of copyright owner]" + * + * Contributor(s): + * If you wish your version of this file to be governed by only the CDDL or + * only the GPL Version 2, indicate your decision by adding "[Contributor] + * elects to include this software in this distribution under the [CDDL or GPL + * Version 2] license." If you don't indicate a single choice of license, a + * recipient has the option to distribute your version of this file under + * either the CDDL, the GPL Version 2 or to extend the choice of license to + * its licensees as provided above. However, if you add GPL Version 2 code + * and therefore, elected the GPL Version 2 license, then the option applies + * only if the new code is made subject to such option by the copyright + * holder. + */ package fish.payara.microprofile.faulttolerance.policy; import java.lang.reflect.Method; diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/BulkheadPolicy.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/BulkheadPolicy.java index b9190bb856e..18a9015bb2f 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/BulkheadPolicy.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/BulkheadPolicy.java @@ -1,3 +1,42 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright (c) 2019 Payara Foundation and/or its affiliates. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common Development + * and Distribution License("CDDL") (collectively, the "License"). You + * may not use this file except in compliance with the License. You can + * obtain a copy of the License at + * https://github.com/payara/Payara/blob/master/LICENSE.txt + * See the License for the specific + * language governing permissions and limitations under the License. + * + * When distributing the software, include this License Header Notice in each + * file and include the License file at glassfish/legal/LICENSE.txt. + * + * GPL Classpath Exception: + * The Payara Foundation designates this particular file as subject to the "Classpath" + * exception as provided by the Payara Foundation in the GPL Version 2 section of the License + * file that accompanied this code. + * + * Modifications: + * If applicable, add the following below the License Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyright [year] [name of copyright owner]" + * + * Contributor(s): + * If you wish your version of this file to be governed by only the CDDL or + * only the GPL Version 2, indicate your decision by adding "[Contributor] + * elects to include this software in this distribution under the [CDDL or GPL + * Version 2] license." If you don't indicate a single choice of license, a + * recipient has the option to distribute your version of this file under + * either the CDDL, the GPL Version 2 or to extend the choice of license to + * its licensees as provided above. However, if you add GPL Version 2 code + * and therefore, elected the GPL Version 2 license, then the option applies + * only if the new code is made subject to such option by the copyright + * holder. + */ package fish.payara.microprofile.faulttolerance.policy; import java.lang.reflect.Method; diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/CircuitBreakerPolicy.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/CircuitBreakerPolicy.java index cb7cd3aa8d3..974475f2bdb 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/CircuitBreakerPolicy.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/CircuitBreakerPolicy.java @@ -1,3 +1,42 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright (c) 2019 Payara Foundation and/or its affiliates. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common Development + * and Distribution License("CDDL") (collectively, the "License"). You + * may not use this file except in compliance with the License. You can + * obtain a copy of the License at + * https://github.com/payara/Payara/blob/master/LICENSE.txt + * See the License for the specific + * language governing permissions and limitations under the License. + * + * When distributing the software, include this License Header Notice in each + * file and include the License file at glassfish/legal/LICENSE.txt. + * + * GPL Classpath Exception: + * The Payara Foundation designates this particular file as subject to the "Classpath" + * exception as provided by the Payara Foundation in the GPL Version 2 section of the License + * file that accompanied this code. + * + * Modifications: + * If applicable, add the following below the License Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyright [year] [name of copyright owner]" + * + * Contributor(s): + * If you wish your version of this file to be governed by only the CDDL or + * only the GPL Version 2, indicate your decision by adding "[Contributor] + * elects to include this software in this distribution under the [CDDL or GPL + * Version 2] license." If you don't indicate a single choice of license, a + * recipient has the option to distribute your version of this file under + * either the CDDL, the GPL Version 2 or to extend the choice of license to + * its licensees as provided above. However, if you add GPL Version 2 code + * and therefore, elected the GPL Version 2 license, then the option applies + * only if the new code is made subject to such option by the copyright + * holder. + */ package fish.payara.microprofile.faulttolerance.policy; import java.lang.reflect.Method; diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/FallbackPolicy.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/FallbackPolicy.java index e10b5d6be08..2af97116cf2 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/FallbackPolicy.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/FallbackPolicy.java @@ -1,3 +1,42 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright (c) 2019 Payara Foundation and/or its affiliates. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common Development + * and Distribution License("CDDL") (collectively, the "License"). You + * may not use this file except in compliance with the License. You can + * obtain a copy of the License at + * https://github.com/payara/Payara/blob/master/LICENSE.txt + * See the License for the specific + * language governing permissions and limitations under the License. + * + * When distributing the software, include this License Header Notice in each + * file and include the License file at glassfish/legal/LICENSE.txt. + * + * GPL Classpath Exception: + * The Payara Foundation designates this particular file as subject to the "Classpath" + * exception as provided by the Payara Foundation in the GPL Version 2 section of the License + * file that accompanied this code. + * + * Modifications: + * If applicable, add the following below the License Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyright [year] [name of copyright owner]" + * + * Contributor(s): + * If you wish your version of this file to be governed by only the CDDL or + * only the GPL Version 2, indicate your decision by adding "[Contributor] + * elects to include this software in this distribution under the [CDDL or GPL + * Version 2] license." If you don't indicate a single choice of license, a + * recipient has the option to distribute your version of this file under + * either the CDDL, the GPL Version 2 or to extend the choice of license to + * its licensees as provided above. However, if you add GPL Version 2 code + * and therefore, elected the GPL Version 2 license, then the option applies + * only if the new code is made subject to such option by the copyright + * holder. + */ package fish.payara.microprofile.faulttolerance.policy; import java.lang.reflect.Method; diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/FaultTolerancePolicy.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/FaultTolerancePolicy.java index 728aa176ec0..ff281ff4aec 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/FaultTolerancePolicy.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/FaultTolerancePolicy.java @@ -1,3 +1,42 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright (c) 2019 Payara Foundation and/or its affiliates. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common Development + * and Distribution License("CDDL") (collectively, the "License"). You + * may not use this file except in compliance with the License. You can + * obtain a copy of the License at + * https://github.com/payara/Payara/blob/master/LICENSE.txt + * See the License for the specific + * language governing permissions and limitations under the License. + * + * When distributing the software, include this License Header Notice in each + * file and include the License file at glassfish/legal/LICENSE.txt. + * + * GPL Classpath Exception: + * The Payara Foundation designates this particular file as subject to the "Classpath" + * exception as provided by the Payara Foundation in the GPL Version 2 section of the License + * file that accompanied this code. + * + * Modifications: + * If applicable, add the following below the License Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyright [year] [name of copyright owner]" + * + * Contributor(s): + * If you wish your version of this file to be governed by only the CDDL or + * only the GPL Version 2, indicate your decision by adding "[Contributor] + * elects to include this software in this distribution under the [CDDL or GPL + * Version 2] license." If you don't indicate a single choice of license, a + * recipient has the option to distribute your version of this file under + * either the CDDL, the GPL Version 2 or to extend the choice of license to + * its licensees as provided above. However, if you add GPL Version 2 code + * and therefore, elected the GPL Version 2 license, then the option applies + * only if the new code is made subject to such option by the copyright + * holder. + */ package fish.payara.microprofile.faulttolerance.policy; import java.io.Serializable; @@ -274,7 +313,7 @@ public boolean completeExceptionally(Throwable ex) { } }; FaultToleranceInvocation invocation = new FaultToleranceInvocation(context, service, metrics, asyncResult, workers); - service.runAsynchronous(asyncResult, + service.runAsynchronous(asyncResult, context, () -> invocation.runStageWithWorker(() -> processFallbackStage(invocation))); return asyncResult; } @@ -349,7 +388,7 @@ private Object processRetryStage(FaultToleranceInvocation invocation) throws Exc private Object processRetryAsync(FaultToleranceInvocation invocation) throws Exception { CompletableFuture asyncAttempt = new CompletableFuture<>(); - invocation.service.runAsynchronous(asyncAttempt, + invocation.service.runAsynchronous(asyncAttempt, invocation.context, () -> invocation.runStageWithWorker(() -> processCircuitBreakerStage(invocation, asyncAttempt))); try { asyncAttempt.get(); // wait and only proceed on success diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/MethodLookupUtils.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/MethodLookupUtils.java index 91d2d5777c3..88c2b272397 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/MethodLookupUtils.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/MethodLookupUtils.java @@ -1,3 +1,42 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright (c) 2019 Payara Foundation and/or its affiliates. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common Development + * and Distribution License("CDDL") (collectively, the "License"). You + * may not use this file except in compliance with the License. You can + * obtain a copy of the License at + * https://github.com/payara/Payara/blob/master/LICENSE.txt + * See the License for the specific + * language governing permissions and limitations under the License. + * + * When distributing the software, include this License Header Notice in each + * file and include the License file at glassfish/legal/LICENSE.txt. + * + * GPL Classpath Exception: + * The Payara Foundation designates this particular file as subject to the "Classpath" + * exception as provided by the Payara Foundation in the GPL Version 2 section of the License + * file that accompanied this code. + * + * Modifications: + * If applicable, add the following below the License Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyright [year] [name of copyright owner]" + * + * Contributor(s): + * If you wish your version of this file to be governed by only the CDDL or + * only the GPL Version 2, indicate your decision by adding "[Contributor] + * elects to include this software in this distribution under the [CDDL or GPL + * Version 2] license." If you don't indicate a single choice of license, a + * recipient has the option to distribute your version of this file under + * either the CDDL, the GPL Version 2 or to extend the choice of license to + * its licensees as provided above. However, if you add GPL Version 2 code + * and therefore, elected the GPL Version 2 license, then the option applies + * only if the new code is made subject to such option by the copyright + * holder. + */ package fish.payara.microprofile.faulttolerance.policy; import java.lang.reflect.GenericArrayType; diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/Policy.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/Policy.java index 3d1a3d1002f..dd954cc6b1f 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/Policy.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/Policy.java @@ -1,3 +1,42 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright (c) 2019 Payara Foundation and/or its affiliates. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common Development + * and Distribution License("CDDL") (collectively, the "License"). You + * may not use this file except in compliance with the License. You can + * obtain a copy of the License at + * https://github.com/payara/Payara/blob/master/LICENSE.txt + * See the License for the specific + * language governing permissions and limitations under the License. + * + * When distributing the software, include this License Header Notice in each + * file and include the License file at glassfish/legal/LICENSE.txt. + * + * GPL Classpath Exception: + * The Payara Foundation designates this particular file as subject to the "Classpath" + * exception as provided by the Payara Foundation in the GPL Version 2 section of the License + * file that accompanied this code. + * + * Modifications: + * If applicable, add the following below the License Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyright [year] [name of copyright owner]" + * + * Contributor(s): + * If you wish your version of this file to be governed by only the CDDL or + * only the GPL Version 2, indicate your decision by adding "[Contributor] + * elects to include this software in this distribution under the [CDDL or GPL + * Version 2] license." If you don't indicate a single choice of license, a + * recipient has the option to distribute your version of this file under + * either the CDDL, the GPL Version 2 or to extend the choice of license to + * its licensees as provided above. However, if you add GPL Version 2 code + * and therefore, elected the GPL Version 2 license, then the option applies + * only if the new code is made subject to such option by the copyright + * holder. + */ package fish.payara.microprofile.faulttolerance.policy; import java.io.Serializable; diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/RetryPolicy.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/RetryPolicy.java index 508b904e6e2..9e91e66e649 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/RetryPolicy.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/RetryPolicy.java @@ -1,3 +1,42 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright (c) 2019 Payara Foundation and/or its affiliates. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common Development + * and Distribution License("CDDL") (collectively, the "License"). You + * may not use this file except in compliance with the License. You can + * obtain a copy of the License at + * https://github.com/payara/Payara/blob/master/LICENSE.txt + * See the License for the specific + * language governing permissions and limitations under the License. + * + * When distributing the software, include this License Header Notice in each + * file and include the License file at glassfish/legal/LICENSE.txt. + * + * GPL Classpath Exception: + * The Payara Foundation designates this particular file as subject to the "Classpath" + * exception as provided by the Payara Foundation in the GPL Version 2 section of the License + * file that accompanied this code. + * + * Modifications: + * If applicable, add the following below the License Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyright [year] [name of copyright owner]" + * + * Contributor(s): + * If you wish your version of this file to be governed by only the CDDL or + * only the GPL Version 2, indicate your decision by adding "[Contributor] + * elects to include this software in this distribution under the [CDDL or GPL + * Version 2] license." If you don't indicate a single choice of license, a + * recipient has the option to distribute your version of this file under + * either the CDDL, the GPL Version 2 or to extend the choice of license to + * its licensees as provided above. However, if you add GPL Version 2 code + * and therefore, elected the GPL Version 2 license, then the option applies + * only if the new code is made subject to such option by the copyright + * holder. + */ package fish.payara.microprofile.faulttolerance.policy; import java.lang.reflect.Method; diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/StaticAnalysisContext.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/StaticAnalysisContext.java index 90630fa4e33..45614d9b6c6 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/StaticAnalysisContext.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/StaticAnalysisContext.java @@ -1,3 +1,42 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright (c) 2019 Payara Foundation and/or its affiliates. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common Development + * and Distribution License("CDDL") (collectively, the "License"). You + * may not use this file except in compliance with the License. You can + * obtain a copy of the License at + * https://github.com/payara/Payara/blob/master/LICENSE.txt + * See the License for the specific + * language governing permissions and limitations under the License. + * + * When distributing the software, include this License Header Notice in each + * file and include the License file at glassfish/legal/LICENSE.txt. + * + * GPL Classpath Exception: + * The Payara Foundation designates this particular file as subject to the "Classpath" + * exception as provided by the Payara Foundation in the GPL Version 2 section of the License + * file that accompanied this code. + * + * Modifications: + * If applicable, add the following below the License Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyright [year] [name of copyright owner]" + * + * Contributor(s): + * If you wish your version of this file to be governed by only the CDDL or + * only the GPL Version 2, indicate your decision by adding "[Contributor] + * elects to include this software in this distribution under the [CDDL or GPL + * Version 2] license." If you don't indicate a single choice of license, a + * recipient has the option to distribute your version of this file under + * either the CDDL, the GPL Version 2 or to extend the choice of license to + * its licensees as provided above. However, if you add GPL Version 2 code + * and therefore, elected the GPL Version 2 license, then the option applies + * only if the new code is made subject to such option by the copyright + * holder. + */ package fish.payara.microprofile.faulttolerance.policy; import java.lang.reflect.Constructor; diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/TimeoutPolicy.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/TimeoutPolicy.java index 0d772b64218..a95e77fcd09 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/TimeoutPolicy.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/TimeoutPolicy.java @@ -1,3 +1,42 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright (c) 2019 Payara Foundation and/or its affiliates. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common Development + * and Distribution License("CDDL") (collectively, the "License"). You + * may not use this file except in compliance with the License. You can + * obtain a copy of the License at + * https://github.com/payara/Payara/blob/master/LICENSE.txt + * See the License for the specific + * language governing permissions and limitations under the License. + * + * When distributing the software, include this License Header Notice in each + * file and include the License file at glassfish/legal/LICENSE.txt. + * + * GPL Classpath Exception: + * The Payara Foundation designates this particular file as subject to the "Classpath" + * exception as provided by the Payara Foundation in the GPL Version 2 section of the License + * file that accompanied this code. + * + * Modifications: + * If applicable, add the following below the License Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyright [year] [name of copyright owner]" + * + * Contributor(s): + * If you wish your version of this file to be governed by only the CDDL or + * only the GPL Version 2, indicate your decision by adding "[Contributor] + * elects to include this software in this distribution under the [CDDL or GPL + * Version 2] license." If you don't indicate a single choice of license, a + * recipient has the option to distribute your version of this file under + * either the CDDL, the GPL Version 2 or to extend the choice of license to + * its licensees as provided above. However, if you add GPL Version 2 code + * and therefore, elected the GPL Version 2 license, then the option applies + * only if the new code is made subject to such option by the copyright + * holder. + */ package fish.payara.microprofile.faulttolerance.policy; import java.lang.reflect.Method; diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/service/BindableFaultToleranceConfig.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/service/BindableFaultToleranceConfig.java index cfb14def2d1..e14e06f54a3 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/service/BindableFaultToleranceConfig.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/service/BindableFaultToleranceConfig.java @@ -1,3 +1,42 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright (c) 2019 Payara Foundation and/or its affiliates. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common Development + * and Distribution License("CDDL") (collectively, the "License"). You + * may not use this file except in compliance with the License. You can + * obtain a copy of the License at + * https://github.com/payara/Payara/blob/master/LICENSE.txt + * See the License for the specific + * language governing permissions and limitations under the License. + * + * When distributing the software, include this License Header Notice in each + * file and include the License file at glassfish/legal/LICENSE.txt. + * + * GPL Classpath Exception: + * The Payara Foundation designates this particular file as subject to the "Classpath" + * exception as provided by the Payara Foundation in the GPL Version 2 section of the License + * file that accompanied this code. + * + * Modifications: + * If applicable, add the following below the License Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyright [year] [name of copyright owner]" + * + * Contributor(s): + * If you wish your version of this file to be governed by only the CDDL or + * only the GPL Version 2, indicate your decision by adding "[Contributor] + * elects to include this software in this distribution under the [CDDL or GPL + * Version 2] license." If you don't indicate a single choice of license, a + * recipient has the option to distribute your version of this file under + * either the CDDL, the GPL Version 2 or to extend the choice of license to + * its licensees as provided above. However, if you add GPL Version 2 code + * and therefore, elected the GPL Version 2 license, then the option applies + * only if the new code is made subject to such option by the copyright + * holder. + */ package fish.payara.microprofile.faulttolerance.service; import java.lang.annotation.Annotation; diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/service/BindableFaultToleranceMetrics.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/service/BindableFaultToleranceMetrics.java index e0c1bd5f0fe..0350546ec31 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/service/BindableFaultToleranceMetrics.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/service/BindableFaultToleranceMetrics.java @@ -1,3 +1,42 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright (c) 2019 Payara Foundation and/or its affiliates. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common Development + * and Distribution License("CDDL") (collectively, the "License"). You + * may not use this file except in compliance with the License. You can + * obtain a copy of the License at + * https://github.com/payara/Payara/blob/master/LICENSE.txt + * See the License for the specific + * language governing permissions and limitations under the License. + * + * When distributing the software, include this License Header Notice in each + * file and include the License file at glassfish/legal/LICENSE.txt. + * + * GPL Classpath Exception: + * The Payara Foundation designates this particular file as subject to the "Classpath" + * exception as provided by the Payara Foundation in the GPL Version 2 section of the License + * file that accompanied this code. + * + * Modifications: + * If applicable, add the following below the License Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyright [year] [name of copyright owner]" + * + * Contributor(s): + * If you wish your version of this file to be governed by only the CDDL or + * only the GPL Version 2, indicate your decision by adding "[Contributor] + * elects to include this software in this distribution under the [CDDL or GPL + * Version 2] license." If you don't indicate a single choice of license, a + * recipient has the option to distribute your version of this file under + * either the CDDL, the GPL Version 2 or to extend the choice of license to + * its licensees as provided above. However, if you add GPL Version 2 code + * and therefore, elected the GPL Version 2 license, then the option applies + * only if the new code is made subject to such option by the copyright + * holder. + */ package fish.payara.microprofile.faulttolerance.service; import java.util.function.LongSupplier; diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/service/FaultToleranceApplicationState.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/service/FaultToleranceApplicationState.java index 875e90dbbdb..6f26ce5d013 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/service/FaultToleranceApplicationState.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/service/FaultToleranceApplicationState.java @@ -1,7 +1,7 @@ /* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * - * Copyright (c) [2018] Payara Foundation and/or its affiliates. All rights reserved. + * Copyright (c) [2018-2019] Payara Foundation and/or its affiliates. All rights reserved. * * The contents of this file are subject to the terms of either the GNU * General Public License Version 2 only ("GPL") or the Common Development diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/service/FaultToleranceExecutionContext.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/service/FaultToleranceExecutionContext.java index c4592cab1e1..911a8a34804 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/service/FaultToleranceExecutionContext.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/service/FaultToleranceExecutionContext.java @@ -1,3 +1,42 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright (c) 2019 Payara Foundation and/or its affiliates. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common Development + * and Distribution License("CDDL") (collectively, the "License"). You + * may not use this file except in compliance with the License. You can + * obtain a copy of the License at + * https://github.com/payara/Payara/blob/master/LICENSE.txt + * See the License for the specific + * language governing permissions and limitations under the License. + * + * When distributing the software, include this License Header Notice in each + * file and include the License file at glassfish/legal/LICENSE.txt. + * + * GPL Classpath Exception: + * The Payara Foundation designates this particular file as subject to the "Classpath" + * exception as provided by the Payara Foundation in the GPL Version 2 section of the License + * file that accompanied this code. + * + * Modifications: + * If applicable, add the following below the License Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyright [year] [name of copyright owner]" + * + * Contributor(s): + * If you wish your version of this file to be governed by only the CDDL or + * only the GPL Version 2, indicate your decision by adding "[Contributor] + * elects to include this software in this distribution under the [CDDL or GPL + * Version 2] license." If you don't indicate a single choice of license, a + * recipient has the option to distribute your version of this file under + * either the CDDL, the GPL Version 2 or to extend the choice of license to + * its licensees as provided above. However, if you add GPL Version 2 code + * and therefore, elected the GPL Version 2 license, then the option applies + * only if the new code is made subject to such option by the copyright + * holder. + */ package fish.payara.microprofile.faulttolerance.service; import java.lang.reflect.Method; diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/service/FaultToleranceServiceImpl.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/service/FaultToleranceServiceImpl.java index de2a58ad0f1..5c8a202b6e8 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/service/FaultToleranceServiceImpl.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/service/FaultToleranceServiceImpl.java @@ -1,7 +1,7 @@ /* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * - * Copyright (c) 2017-2018 Payara Foundation and/or its affiliates. All rights reserved. + * Copyright (c) 2017-2019 Payara Foundation and/or its affiliates. All rights reserved. * * The contents of this file are subject to the terms of either the GNU * General Public License Version 2 only ("GPL") or the Common Development @@ -44,6 +44,7 @@ import fish.payara.microprofile.faulttolerance.FaultToleranceService; import fish.payara.microprofile.faulttolerance.FaultToleranceServiceConfiguration; import fish.payara.microprofile.faulttolerance.policy.AsynchronousPolicy; +import fish.payara.microprofile.faulttolerance.policy.FaultTolerancePolicy; import fish.payara.microprofile.faulttolerance.state.BulkheadSemaphore; import fish.payara.microprofile.faulttolerance.state.CircuitBreakerState; import fish.payara.notification.requesttracing.RequestTraceSpan; @@ -138,6 +139,7 @@ public void event(Event event) { if (event.is(Deployment.APPLICATION_UNLOADED)) { ApplicationInfo info = (ApplicationInfo) event.hook(); deregisterApplication(info.getName()); + FaultTolerancePolicy.clean(); } } @@ -155,8 +157,6 @@ public FaultToleranceMetrics getMetrics(InvocationContext context) { metrics -> metrics != null ? metrics : new BindableFaultToleranceMetrics()).bindTo(context); } - //TODO use the scheduler to schedule a clean of FT Info - private ManagedExecutorService getManagedExecutorService() { return lookup(serviceConfig.getManagedExecutorService(), defaultExecutorService); } @@ -314,11 +314,12 @@ public void delay(long delayMillis, InvocationContext context) throws Interrupte } @Override - public void runAsynchronous(CompletableFuture asyncResult, Callable task) + public void runAsynchronous(CompletableFuture asyncResult, InvocationContext context, Callable task) throws RejectedExecutionException { Runnable completionTask = () -> { if (!asyncResult.isCancelled() && !Thread.currentThread().isInterrupted()) { try { + trace("runAsynchronous", context); Future futureResult = AsynchronousPolicy.toFuture(task.call()); if (!asyncResult.isCancelled()) { // could be cancelled in the meanwhile if (!asyncResult.isDone()) { @@ -330,6 +331,8 @@ public void runAsynchronous(CompletableFuture asyncResult, Callable annotationType); + /** + * The sets of annotations the given sterotype annotation represents. + * + * @param stereotype a sterotype annotation ({@link #isStereotype(Class)} was {@code true}) + * @return the set of annotation defined as sterotype + */ Set getStereotypeDefinition(Class stereotype); } \ No newline at end of file From 0df07f6c33445c1c31754bc52eccf66e0eb15b57 Mon Sep 17 00:00:00 2001 From: Jan Bernitt Date: Mon, 22 Apr 2019 10:40:32 +0200 Subject: [PATCH 15/30] PAYARA-3468 more javadoc --- .../service/BindableFaultToleranceConfig.java | 10 ++++++++++ .../service/BindableFaultToleranceMetrics.java | 10 ++++++++++ 2 files changed, 20 insertions(+) diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/service/BindableFaultToleranceConfig.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/service/BindableFaultToleranceConfig.java index e14e06f54a3..7bcd40d5c87 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/service/BindableFaultToleranceConfig.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/service/BindableFaultToleranceConfig.java @@ -114,6 +114,16 @@ private static Config resolveConfig() { * Factory method */ + /** + * Creates a {@link FaultToleranceConfig} that is bound to the given {@link InvocationContext} that is currently + * processed. + * + * Implementation note: If this configuration template has no {@link Config} available it falls back to pure + * annotation lookup. + * + * @param context the currently processed context to bind to + * @return A {@link FaultToleranceConfig} bound to the given context + */ public FaultToleranceConfig bindTo(InvocationContext context) { return config == null ? FaultToleranceConfig.asAnnotated(context.getTarget().getClass(), context.getMethod()) diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/service/BindableFaultToleranceMetrics.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/service/BindableFaultToleranceMetrics.java index 0350546ec31..67a2755461e 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/service/BindableFaultToleranceMetrics.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/service/BindableFaultToleranceMetrics.java @@ -95,6 +95,16 @@ private static MetricRegistry resolveRegistry() { * Factory method */ + /** + * Creates a {@link FaultToleranceMetrics} that is bound to the given {@link InvocationContext} that is currently + * processed. + * + * Implementation note: If the {@link MetricRegistry} could not be resolved the + * {@link FaultToleranceMetrics#DISABLED} is returned. + * + * @param context the currently processed context to bind to + * @return a {@link FaultToleranceMetrics} bound to the given context + */ FaultToleranceMetrics bindTo(InvocationContext context) { return metricRegistry == null ? FaultToleranceMetrics.DISABLED From 6271fdeea76202a1f24cf848de48668942146cf5 Mon Sep 17 00:00:00 2001 From: Jan Bernitt Date: Mon, 22 Apr 2019 12:38:06 +0200 Subject: [PATCH 16/30] PAYARA-3468 added tests for config overrides --- .../policy/StaticAnalysisContext.java | 2 +- .../service/BindableFaultToleranceConfig.java | 6 +- .../service/ConfigOverrideTest.java | 111 ++++++++++++++++++ .../faulttolerance/test/TestUtils.java | 3 +- 4 files changed, 119 insertions(+), 3 deletions(-) create mode 100644 appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/service/ConfigOverrideTest.java diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/StaticAnalysisContext.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/StaticAnalysisContext.java index 45614d9b6c6..fbddc21a85f 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/StaticAnalysisContext.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/StaticAnalysisContext.java @@ -50,7 +50,7 @@ * * @author Jan Bernitt */ -final class StaticAnalysisContext implements InvocationContext { +public final class StaticAnalysisContext implements InvocationContext { private final Class targetClass; private final Method annotated; diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/service/BindableFaultToleranceConfig.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/service/BindableFaultToleranceConfig.java index 7bcd40d5c87..a0f7144b2d2 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/service/BindableFaultToleranceConfig.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/service/BindableFaultToleranceConfig.java @@ -84,8 +84,12 @@ final class BindableFaultToleranceConfig implements FaultToleranceConfig { private final InvocationContext context; public BindableFaultToleranceConfig(Stereotypes sterotypes) { + this(resolveConfig(), sterotypes); + } + + public BindableFaultToleranceConfig(Config config, Stereotypes sterotypes) { + this.config = config; this.sterotypes = sterotypes; - this.config = resolveConfig(); this.nonFallbackEnabled = new AtomicReference<>(); this.metricsEnabled = new AtomicReference<>(); this.context = null; // factory is unbound diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/service/ConfigOverrideTest.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/service/ConfigOverrideTest.java new file mode 100644 index 00000000000..aa04423e479 --- /dev/null +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/service/ConfigOverrideTest.java @@ -0,0 +1,111 @@ +package fish.payara.microprofile.faulttolerance.service; + +import static fish.payara.microprofile.faulttolerance.test.TestUtils.getAnnotatedMethod; +import static org.junit.Assert.assertEquals; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.time.temporal.ChronoUnit; +import java.util.Optional; +import java.util.Properties; +import java.util.function.BiFunction; + +import org.eclipse.microprofile.config.Config; +import org.eclipse.microprofile.config.spi.ConfigSource; +import org.eclipse.microprofile.faulttolerance.CircuitBreaker; +import org.eclipse.microprofile.faulttolerance.Timeout; +import org.junit.Test; + +import fish.payara.microprofile.faulttolerance.FaultToleranceConfig; +import fish.payara.microprofile.faulttolerance.policy.StaticAnalysisContext; + +/** + * Tests that properties can be used to override annotation attributes. + * + * In response to: + * + * - https://github.com/payara/Payara/issues/3762 + * - https://github.com/payara/Payara/issues/3821 + * + * @author Jan Bernitt + */ +public class ConfigOverrideTest implements Config { + + private BindableFaultToleranceConfig config = new BindableFaultToleranceConfig(this, null); + private final Properties overrides = new Properties(); + + @SuppressWarnings("unchecked") + @Override + public Optional getOptionalValue(String propertyName, Class propertyType) { + String value = overrides.getProperty(propertyName); + if (value == null) { + return Optional.empty(); + } + if (propertyType == String.class) { + return (Optional) Optional.of(value); + } + if (propertyType == Integer.class) { + return (Optional) Optional.of(Integer.valueOf(value)); + } + if (propertyType.isEnum()) { + return (Optional) Optional.of(enumValue(propertyType, value)); + } + throw new UnsupportedOperationException(); + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + private static > Enum enumValue(Class type, String value) { + return Enum.valueOf(type, value.toUpperCase()); + } + + @Override + public T getValue(String propertyName, Class propertyType) { + throw new UnsupportedOperationException(); + } + + @Override + public Iterable getPropertyNames() { + throw new UnsupportedOperationException(); + } + + @Override + public Iterable getConfigSources() { + throw new UnsupportedOperationException(); + } + + @Test + public void circuitBreakerRequestVolumeThresholdOverride() { + assertOverridden(CircuitBreaker.class, "requestVolumeThreshold", 42, 13, + (config, annotation) -> config.requestVolumeThreshold(annotation)); + } + + @CircuitBreaker(requestVolumeThreshold = 42) + public String circuitBreakerRequestVolumeThresholdOverride_Method() { + return "test"; + } + + @Test + public void timeoutUnitOverride() { + assertOverridden(Timeout.class, "unit", ChronoUnit.DECADES, ChronoUnit.HOURS, + (config, annotation) -> config.unit(annotation)); + } + + @Timeout(unit = ChronoUnit.DECADES) + public String timeoutUnitOverride_Method() { + return "test"; + } + + private void assertOverridden(Class annotationType, String propertyName, T annotated, + T overridden, BiFunction property) { + Method annotatedMethod = getAnnotatedMethod(); + A annotation = annotatedMethod.getAnnotation(annotationType); + FaultToleranceConfig boundConfig = config.bindTo(new StaticAnalysisContext(getClass(), annotatedMethod)); + // check we get the expected annotated value + assertEquals(annotated, property.apply(boundConfig, annotation)); + // make the override + overrides.put(String.format("%s/%s/%s/%s", annotatedMethod.getDeclaringClass().getName(), + annotatedMethod.getName(), annotationType.getSimpleName(), propertyName), overridden.toString()); + // now check that we get the expected overridden value + assertEquals(overridden, property.apply(boundConfig, annotation)); + } +} diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/test/TestUtils.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/test/TestUtils.java index a517e40dc11..8b39060d870 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/test/TestUtils.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/test/TestUtils.java @@ -53,7 +53,8 @@ public static void assertAnnotationInvalid(String expectedErrorMessage) { public static Method getAnnotatedMethod() { StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace(); int i = 0; - while (!stackTraceElements[i].getClassName().endsWith("Test")) { + while (!stackTraceElements[i].getClassName().endsWith("Test") + || stackTraceElements[i].getMethodName().startsWith("assert")) { i++; } StackTraceElement testMethodElement = stackTraceElements[i]; From d3cc6b1532f6db794ecd83f982d8da37532c381b Mon Sep 17 00:00:00 2001 From: Jan Bernitt Date: Mon, 22 Apr 2019 14:35:44 +0200 Subject: [PATCH 17/30] PAYARA-3468 added tests for config override - all different value types; added copyright header to tests --- .../policy/AnnotationInvalidTest.java | 39 ++++ .../policy/FallbackInvalidTest.java | 39 ++++ .../policy/FallbackMethodBean.java | 39 ++++ .../policy/FallbackMethodBeanA.java | 39 ++++ .../policy/FallbackMethodBeanB.java | 39 ++++ .../policy/FallbackMethodTest.java | 39 ++++ .../service/ConfigOverridePriorityTest.java | 47 +++++ .../service/ConfigOverrideTest.java | 180 +++++++++++++----- .../faulttolerance/test/ConfigOverrides.java | 127 ++++++++++++ .../faulttolerance/test/TestUtils.java | 39 ++++ 10 files changed, 578 insertions(+), 49 deletions(-) create mode 100644 appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/service/ConfigOverridePriorityTest.java create mode 100644 appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/test/ConfigOverrides.java diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/policy/AnnotationInvalidTest.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/policy/AnnotationInvalidTest.java index 52e32c432cb..a87a8990cc0 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/policy/AnnotationInvalidTest.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/policy/AnnotationInvalidTest.java @@ -1,3 +1,42 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright (c) 2019 Payara Foundation and/or its affiliates. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common Development + * and Distribution License("CDDL") (collectively, the "License"). You + * may not use this file except in compliance with the License. You can + * obtain a copy of the License at + * https://github.com/payara/Payara/blob/master/LICENSE.txt + * See the License for the specific + * language governing permissions and limitations under the License. + * + * When distributing the software, include this License Header Notice in each + * file and include the License file at glassfish/legal/LICENSE.txt. + * + * GPL Classpath Exception: + * The Payara Foundation designates this particular file as subject to the "Classpath" + * exception as provided by the Payara Foundation in the GPL Version 2 section of the License + * file that accompanied this code. + * + * Modifications: + * If applicable, add the following below the License Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyright [year] [name of copyright owner]" + * + * Contributor(s): + * If you wish your version of this file to be governed by only the CDDL or + * only the GPL Version 2, indicate your decision by adding "[Contributor] + * elects to include this software in this distribution under the [CDDL or GPL + * Version 2] license." If you don't indicate a single choice of license, a + * recipient has the option to distribute your version of this file under + * either the CDDL, the GPL Version 2 or to extend the choice of license to + * its licensees as provided above. However, if you add GPL Version 2 code + * and therefore, elected the GPL Version 2 license, then the option applies + * only if the new code is made subject to such option by the copyright + * holder. + */ package fish.payara.microprofile.faulttolerance.policy; import static fish.payara.microprofile.faulttolerance.test.TestUtils.assertAnnotationInvalid; diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/policy/FallbackInvalidTest.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/policy/FallbackInvalidTest.java index 529a2f37718..c9eb23dc642 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/policy/FallbackInvalidTest.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/policy/FallbackInvalidTest.java @@ -1,3 +1,42 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright (c) 2019 Payara Foundation and/or its affiliates. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common Development + * and Distribution License("CDDL") (collectively, the "License"). You + * may not use this file except in compliance with the License. You can + * obtain a copy of the License at + * https://github.com/payara/Payara/blob/master/LICENSE.txt + * See the License for the specific + * language governing permissions and limitations under the License. + * + * When distributing the software, include this License Header Notice in each + * file and include the License file at glassfish/legal/LICENSE.txt. + * + * GPL Classpath Exception: + * The Payara Foundation designates this particular file as subject to the "Classpath" + * exception as provided by the Payara Foundation in the GPL Version 2 section of the License + * file that accompanied this code. + * + * Modifications: + * If applicable, add the following below the License Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyright [year] [name of copyright owner]" + * + * Contributor(s): + * If you wish your version of this file to be governed by only the CDDL or + * only the GPL Version 2, indicate your decision by adding "[Contributor] + * elects to include this software in this distribution under the [CDDL or GPL + * Version 2] license." If you don't indicate a single choice of license, a + * recipient has the option to distribute your version of this file under + * either the CDDL, the GPL Version 2 or to extend the choice of license to + * its licensees as provided above. However, if you add GPL Version 2 code + * and therefore, elected the GPL Version 2 license, then the option applies + * only if the new code is made subject to such option by the copyright + * holder. + */ package fish.payara.microprofile.faulttolerance.policy; import static fish.payara.microprofile.faulttolerance.test.TestUtils.assertAnnotationInvalid; diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/policy/FallbackMethodBean.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/policy/FallbackMethodBean.java index 52651ab0a8a..2810a16d8e3 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/policy/FallbackMethodBean.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/policy/FallbackMethodBean.java @@ -1,3 +1,42 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright (c) 2019 Payara Foundation and/or its affiliates. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common Development + * and Distribution License("CDDL") (collectively, the "License"). You + * may not use this file except in compliance with the License. You can + * obtain a copy of the License at + * https://github.com/payara/Payara/blob/master/LICENSE.txt + * See the License for the specific + * language governing permissions and limitations under the License. + * + * When distributing the software, include this License Header Notice in each + * file and include the License file at glassfish/legal/LICENSE.txt. + * + * GPL Classpath Exception: + * The Payara Foundation designates this particular file as subject to the "Classpath" + * exception as provided by the Payara Foundation in the GPL Version 2 section of the License + * file that accompanied this code. + * + * Modifications: + * If applicable, add the following below the License Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyright [year] [name of copyright owner]" + * + * Contributor(s): + * If you wish your version of this file to be governed by only the CDDL or + * only the GPL Version 2, indicate your decision by adding "[Contributor] + * elects to include this software in this distribution under the [CDDL or GPL + * Version 2] license." If you don't indicate a single choice of license, a + * recipient has the option to distribute your version of this file under + * either the CDDL, the GPL Version 2 or to extend the choice of license to + * its licensees as provided above. However, if you add GPL Version 2 code + * and therefore, elected the GPL Version 2 license, then the option applies + * only if the new code is made subject to such option by the copyright + * holder. + */ package fish.payara.microprofile.faulttolerance.policy; @SuppressWarnings("unused") diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/policy/FallbackMethodBeanA.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/policy/FallbackMethodBeanA.java index 6d8dc89597f..d2c7991d958 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/policy/FallbackMethodBeanA.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/policy/FallbackMethodBeanA.java @@ -1,3 +1,42 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright (c) 2019 Payara Foundation and/or its affiliates. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common Development + * and Distribution License("CDDL") (collectively, the "License"). You + * may not use this file except in compliance with the License. You can + * obtain a copy of the License at + * https://github.com/payara/Payara/blob/master/LICENSE.txt + * See the License for the specific + * language governing permissions and limitations under the License. + * + * When distributing the software, include this License Header Notice in each + * file and include the License file at glassfish/legal/LICENSE.txt. + * + * GPL Classpath Exception: + * The Payara Foundation designates this particular file as subject to the "Classpath" + * exception as provided by the Payara Foundation in the GPL Version 2 section of the License + * file that accompanied this code. + * + * Modifications: + * If applicable, add the following below the License Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyright [year] [name of copyright owner]" + * + * Contributor(s): + * If you wish your version of this file to be governed by only the CDDL or + * only the GPL Version 2, indicate your decision by adding "[Contributor] + * elects to include this software in this distribution under the [CDDL or GPL + * Version 2] license." If you don't indicate a single choice of license, a + * recipient has the option to distribute your version of this file under + * either the CDDL, the GPL Version 2 or to extend the choice of license to + * its licensees as provided above. However, if you add GPL Version 2 code + * and therefore, elected the GPL Version 2 license, then the option applies + * only if the new code is made subject to such option by the copyright + * holder. + */ package fish.payara.microprofile.faulttolerance.policy; import static org.junit.Assert.fail; diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/policy/FallbackMethodBeanB.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/policy/FallbackMethodBeanB.java index cd67f8b278d..55b596237e4 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/policy/FallbackMethodBeanB.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/policy/FallbackMethodBeanB.java @@ -1,3 +1,42 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright (c) 2019 Payara Foundation and/or its affiliates. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common Development + * and Distribution License("CDDL") (collectively, the "License"). You + * may not use this file except in compliance with the License. You can + * obtain a copy of the License at + * https://github.com/payara/Payara/blob/master/LICENSE.txt + * See the License for the specific + * language governing permissions and limitations under the License. + * + * When distributing the software, include this License Header Notice in each + * file and include the License file at glassfish/legal/LICENSE.txt. + * + * GPL Classpath Exception: + * The Payara Foundation designates this particular file as subject to the "Classpath" + * exception as provided by the Payara Foundation in the GPL Version 2 section of the License + * file that accompanied this code. + * + * Modifications: + * If applicable, add the following below the License Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyright [year] [name of copyright owner]" + * + * Contributor(s): + * If you wish your version of this file to be governed by only the CDDL or + * only the GPL Version 2, indicate your decision by adding "[Contributor] + * elects to include this software in this distribution under the [CDDL or GPL + * Version 2] license." If you don't indicate a single choice of license, a + * recipient has the option to distribute your version of this file under + * either the CDDL, the GPL Version 2 or to extend the choice of license to + * its licensees as provided above. However, if you add GPL Version 2 code + * and therefore, elected the GPL Version 2 license, then the option applies + * only if the new code is made subject to such option by the copyright + * holder. + */ package fish.payara.microprofile.faulttolerance.policy; import static org.junit.Assert.fail; diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/policy/FallbackMethodTest.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/policy/FallbackMethodTest.java index 553c4dd82c7..187af5947e5 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/policy/FallbackMethodTest.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/policy/FallbackMethodTest.java @@ -1,3 +1,42 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright (c) 2019 Payara Foundation and/or its affiliates. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common Development + * and Distribution License("CDDL") (collectively, the "License"). You + * may not use this file except in compliance with the License. You can + * obtain a copy of the License at + * https://github.com/payara/Payara/blob/master/LICENSE.txt + * See the License for the specific + * language governing permissions and limitations under the License. + * + * When distributing the software, include this License Header Notice in each + * file and include the License file at glassfish/legal/LICENSE.txt. + * + * GPL Classpath Exception: + * The Payara Foundation designates this particular file as subject to the "Classpath" + * exception as provided by the Payara Foundation in the GPL Version 2 section of the License + * file that accompanied this code. + * + * Modifications: + * If applicable, add the following below the License Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyright [year] [name of copyright owner]" + * + * Contributor(s): + * If you wish your version of this file to be governed by only the CDDL or + * only the GPL Version 2, indicate your decision by adding "[Contributor] + * elects to include this software in this distribution under the [CDDL or GPL + * Version 2] license." If you don't indicate a single choice of license, a + * recipient has the option to distribute your version of this file under + * either the CDDL, the GPL Version 2 or to extend the choice of license to + * its licensees as provided above. However, if you add GPL Version 2 code + * and therefore, elected the GPL Version 2 license, then the option applies + * only if the new code is made subject to such option by the copyright + * holder. + */ package fish.payara.microprofile.faulttolerance.policy; import static org.junit.Assert.assertEquals; diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/service/ConfigOverridePriorityTest.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/service/ConfigOverridePriorityTest.java new file mode 100644 index 00000000000..7326282e80c --- /dev/null +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/service/ConfigOverridePriorityTest.java @@ -0,0 +1,47 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright (c) 2019 Payara Foundation and/or its affiliates. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common Development + * and Distribution License("CDDL") (collectively, the "License"). You + * may not use this file except in compliance with the License. You can + * obtain a copy of the License at + * https://github.com/payara/Payara/blob/master/LICENSE.txt + * See the License for the specific + * language governing permissions and limitations under the License. + * + * When distributing the software, include this License Header Notice in each + * file and include the License file at glassfish/legal/LICENSE.txt. + * + * GPL Classpath Exception: + * The Payara Foundation designates this particular file as subject to the "Classpath" + * exception as provided by the Payara Foundation in the GPL Version 2 section of the License + * file that accompanied this code. + * + * Modifications: + * If applicable, add the following below the License Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyright [year] [name of copyright owner]" + * + * Contributor(s): + * If you wish your version of this file to be governed by only the CDDL or + * only the GPL Version 2, indicate your decision by adding "[Contributor] + * elects to include this software in this distribution under the [CDDL or GPL + * Version 2] license." If you don't indicate a single choice of license, a + * recipient has the option to distribute your version of this file under + * either the CDDL, the GPL Version 2 or to extend the choice of license to + * its licensees as provided above. However, if you add GPL Version 2 code + * and therefore, elected the GPL Version 2 license, then the option applies + * only if the new code is made subject to such option by the copyright + * holder. + */ +package fish.payara.microprofile.faulttolerance.service; + +public class ConfigOverridePriorityTest { + + //TODO override prio test + + //TODO enabled override prio test +} diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/service/ConfigOverrideTest.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/service/ConfigOverrideTest.java index aa04423e479..e7ec00ac0a8 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/service/ConfigOverrideTest.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/service/ConfigOverrideTest.java @@ -1,23 +1,64 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright (c) 2019 Payara Foundation and/or its affiliates. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common Development + * and Distribution License("CDDL") (collectively, the "License"). You + * may not use this file except in compliance with the License. You can + * obtain a copy of the License at + * https://github.com/payara/Payara/blob/master/LICENSE.txt + * See the License for the specific + * language governing permissions and limitations under the License. + * + * When distributing the software, include this License Header Notice in each + * file and include the License file at glassfish/legal/LICENSE.txt. + * + * GPL Classpath Exception: + * The Payara Foundation designates this particular file as subject to the "Classpath" + * exception as provided by the Payara Foundation in the GPL Version 2 section of the License + * file that accompanied this code. + * + * Modifications: + * If applicable, add the following below the License Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyright [year] [name of copyright owner]" + * + * Contributor(s): + * If you wish your version of this file to be governed by only the CDDL or + * only the GPL Version 2, indicate your decision by adding "[Contributor] + * elects to include this software in this distribution under the [CDDL or GPL + * Version 2] license." If you don't indicate a single choice of license, a + * recipient has the option to distribute your version of this file under + * either the CDDL, the GPL Version 2 or to extend the choice of license to + * its licensees as provided above. However, if you add GPL Version 2 code + * and therefore, elected the GPL Version 2 license, then the option applies + * only if the new code is made subject to such option by the copyright + * holder. + */ package fish.payara.microprofile.faulttolerance.service; import static fish.payara.microprofile.faulttolerance.test.TestUtils.getAnnotatedMethod; +import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.time.temporal.ChronoUnit; -import java.util.Optional; -import java.util.Properties; +import java.util.NoSuchElementException; import java.util.function.BiFunction; -import org.eclipse.microprofile.config.Config; -import org.eclipse.microprofile.config.spi.ConfigSource; import org.eclipse.microprofile.faulttolerance.CircuitBreaker; +import org.eclipse.microprofile.faulttolerance.Fallback; +import org.eclipse.microprofile.faulttolerance.FallbackHandler; +import org.eclipse.microprofile.faulttolerance.Retry; import org.eclipse.microprofile.faulttolerance.Timeout; import org.junit.Test; import fish.payara.microprofile.faulttolerance.FaultToleranceConfig; import fish.payara.microprofile.faulttolerance.policy.StaticAnalysisContext; +import fish.payara.microprofile.faulttolerance.test.ConfigOverrides; /** * Tests that properties can be used to override annotation attributes. @@ -29,50 +70,18 @@ * * @author Jan Bernitt */ -public class ConfigOverrideTest implements Config { +public class ConfigOverrideTest { - private BindableFaultToleranceConfig config = new BindableFaultToleranceConfig(this, null); - private final Properties overrides = new Properties(); + private ConfigOverrides overrides = new ConfigOverrides(); + private BindableFaultToleranceConfig config = new BindableFaultToleranceConfig(overrides, null); - @SuppressWarnings("unchecked") - @Override - public Optional getOptionalValue(String propertyName, Class propertyType) { - String value = overrides.getProperty(propertyName); - if (value == null) { - return Optional.empty(); - } - if (propertyType == String.class) { - return (Optional) Optional.of(value); - } - if (propertyType == Integer.class) { - return (Optional) Optional.of(Integer.valueOf(value)); - } - if (propertyType.isEnum()) { - return (Optional) Optional.of(enumValue(propertyType, value)); - } - throw new UnsupportedOperationException(); - } - - @SuppressWarnings({ "rawtypes", "unchecked" }) - private static > Enum enumValue(Class type, String value) { - return Enum.valueOf(type, value.toUpperCase()); - } - - @Override - public T getValue(String propertyName, Class propertyType) { - throw new UnsupportedOperationException(); - } - - @Override - public Iterable getPropertyNames() { - throw new UnsupportedOperationException(); - } - - @Override - public Iterable getConfigSources() { - throw new UnsupportedOperationException(); - } + /* + * Tests + */ + /** + * Tests a int/{@link Integer} value override + */ @Test public void circuitBreakerRequestVolumeThresholdOverride() { assertOverridden(CircuitBreaker.class, "requestVolumeThreshold", 42, 13, @@ -84,6 +93,9 @@ public String circuitBreakerRequestVolumeThresholdOverride_Method() { return "test"; } + /** + * Tests a {@link ChronoUnit} value override + */ @Test public void timeoutUnitOverride() { assertOverridden(Timeout.class, "unit", ChronoUnit.DECADES, ChronoUnit.HOURS, @@ -95,17 +107,87 @@ public String timeoutUnitOverride_Method() { return "test"; } + /** + * Tests a {@link Class} value override + */ + @Test + public void fallbackValueOverride() { + assertOverridden(Fallback.class, "value", MyFallbackHandler.class, MyOtherFallbackHandler.class, + (config, annotation) -> config.value(annotation)); + } + + @Fallback(value = MyFallbackHandler.class) + public String fallbackValueOverride_Method() { + return "test"; + } + + /** + * Tests a {@link String} value override + */ + @Test + public void fallbackFallbackMethodOverride() { + assertOverridden(Fallback.class, "fallbackMethod", "annotatedFallbackMethod", "overriddenFallbackMethod", + (config, annotation) -> config.fallbackMethod(annotation)); + } + + @Fallback(fallbackMethod = "annotatedFallbackMethod") + public String fallbackFallbackMethodOverride_Method() { + return "test"; + } + + /** + * Tests a long/{@link Long} value override + */ + @Test + public void retryDelayOverride() { + assertOverridden(Retry.class, "delay", 54321L, 12345L, + (config, annotation) -> config.delay(annotation)); + } + + @Retry(delay = 54321) + public String retryDelayOverride_Method() { + return "test"; + } + + /** + * Tests a {@link Class[]} value override + */ + @Test + public void retryRetryOnOverride() { + assertOverridden(Retry.class, "retryOn", + new Class[] { IllegalStateException.class, IllegalArgumentException.class }, + new Class[] { NoSuchElementException.class, UnsupportedOperationException.class }, + (config, annotation) -> config.retryOn(annotation)); + } + + @Retry(retryOn = { IllegalStateException.class, IllegalArgumentException.class }) + public String retryRetryOnOverride_Method() { + return "test"; + } + private void assertOverridden(Class annotationType, String propertyName, T annotated, T overridden, BiFunction property) { Method annotatedMethod = getAnnotatedMethod(); A annotation = annotatedMethod.getAnnotation(annotationType); FaultToleranceConfig boundConfig = config.bindTo(new StaticAnalysisContext(getClass(), annotatedMethod)); // check we get the expected annotated value - assertEquals(annotated, property.apply(boundConfig, annotation)); + T actual = property.apply(boundConfig, annotation); + assertEqualValue(annotated, actual); // make the override - overrides.put(String.format("%s/%s/%s/%s", annotatedMethod.getDeclaringClass().getName(), - annotatedMethod.getName(), annotationType.getSimpleName(), propertyName), overridden.toString()); + overrides.override(annotatedMethod, annotationType, propertyName, overridden); // now check that we get the expected overridden value - assertEquals(overridden, property.apply(boundConfig, annotation)); + actual = property.apply(boundConfig, annotation); + assertEqualValue(overridden, actual); } + + private static void assertEqualValue(T expected, T actual) { + if (actual instanceof Object[]) { + assertArrayEquals((Object[]) expected, (Object[])actual); + } else { + assertEquals(expected, actual); + } + } + + interface MyFallbackHandler extends FallbackHandler {/* just for config override test */} + interface MyOtherFallbackHandler extends FallbackHandler {/* just for config override test */} } diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/test/ConfigOverrides.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/test/ConfigOverrides.java new file mode 100644 index 00000000000..ebc8a2624b7 --- /dev/null +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/test/ConfigOverrides.java @@ -0,0 +1,127 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright (c) 2019 Payara Foundation and/or its affiliates. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common Development + * and Distribution License("CDDL") (collectively, the "License"). You + * may not use this file except in compliance with the License. You can + * obtain a copy of the License at + * https://github.com/payara/Payara/blob/master/LICENSE.txt + * See the License for the specific + * language governing permissions and limitations under the License. + * + * When distributing the software, include this License Header Notice in each + * file and include the License file at glassfish/legal/LICENSE.txt. + * + * GPL Classpath Exception: + * The Payara Foundation designates this particular file as subject to the "Classpath" + * exception as provided by the Payara Foundation in the GPL Version 2 section of the License + * file that accompanied this code. + * + * Modifications: + * If applicable, add the following below the License Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyright [year] [name of copyright owner]" + * + * Contributor(s): + * If you wish your version of this file to be governed by only the CDDL or + * only the GPL Version 2, indicate your decision by adding "[Contributor] + * elects to include this software in this distribution under the [CDDL or GPL + * Version 2] license." If you don't indicate a single choice of license, a + * recipient has the option to distribute your version of this file under + * either the CDDL, the GPL Version 2 or to extend the choice of license to + * its licensees as provided above. However, if you add GPL Version 2 code + * and therefore, elected the GPL Version 2 license, then the option applies + * only if the new code is made subject to such option by the copyright + * holder. + */ +package fish.payara.microprofile.faulttolerance.test; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.util.Optional; +import java.util.Properties; + +import org.eclipse.microprofile.config.Config; +import org.eclipse.microprofile.config.spi.ConfigSource; + +/** + * A {@link Config} implementations for testing where properties can be set using the + * {@link #override(Method, Class, String, Object)} methods. + * + * The implementation will use the to and from {@link String} conversion to make sure the usage is proper. + * + * @author Jan Bernitt + */ +public final class ConfigOverrides implements Config { + + private final Properties overrides = new Properties(); + + public void override(Method annotatedMethod, Class annotationType, String propertyName, + Object value) { + overrides.put(String.format("%s/%s/%s/%s", annotatedMethod.getDeclaringClass().getName(), + annotatedMethod.getName(), annotationType.getSimpleName(), propertyName), toString(value)); + } + + private static String toString(Object value) { + if (value instanceof Class) { + return ((Class) value).getName(); + } + if (value instanceof Class[]) { + Class[] classes = (Class[]) value; + String classNameList = ""; + for (int i = 0; i < classes.length; i++) { + if (i > 0) { + classNameList += ","; + } + classNameList += classes[i].getName(); + } + return classNameList; + } + return value.toString(); + } + + @SuppressWarnings("unchecked") + @Override + public Optional getOptionalValue(String propertyName, Class propertyType) { + String value = overrides.getProperty(propertyName); + if (value == null) { + return Optional.empty(); + } + if (propertyType == String.class) { + return (Optional) Optional.of(value); + } + if (propertyType == Integer.class) { + return (Optional) Optional.of(Integer.valueOf(value)); + } + if (propertyType == Long.class) { + return (Optional) Optional.of(Long.valueOf(value)); + } + if (propertyType.isEnum()) { + return (Optional) Optional.of(enumValue(propertyType, value)); + } + throw new UnsupportedOperationException(); + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + private static > Enum enumValue(Class type, String value) { + return Enum.valueOf(type, value.toUpperCase()); + } + + @Override + public T getValue(String propertyName, Class propertyType) { + throw new UnsupportedOperationException(); + } + + @Override + public Iterable getPropertyNames() { + throw new UnsupportedOperationException(); + } + + @Override + public Iterable getConfigSources() { + throw new UnsupportedOperationException(); + } +} diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/test/TestUtils.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/test/TestUtils.java index 8b39060d870..09f2b6c02d8 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/test/TestUtils.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/test/TestUtils.java @@ -1,3 +1,42 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright (c) 2019 Payara Foundation and/or its affiliates. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common Development + * and Distribution License("CDDL") (collectively, the "License"). You + * may not use this file except in compliance with the License. You can + * obtain a copy of the License at + * https://github.com/payara/Payara/blob/master/LICENSE.txt + * See the License for the specific + * language governing permissions and limitations under the License. + * + * When distributing the software, include this License Header Notice in each + * file and include the License file at glassfish/legal/LICENSE.txt. + * + * GPL Classpath Exception: + * The Payara Foundation designates this particular file as subject to the "Classpath" + * exception as provided by the Payara Foundation in the GPL Version 2 section of the License + * file that accompanied this code. + * + * Modifications: + * If applicable, add the following below the License Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyright [year] [name of copyright owner]" + * + * Contributor(s): + * If you wish your version of this file to be governed by only the CDDL or + * only the GPL Version 2, indicate your decision by adding "[Contributor] + * elects to include this software in this distribution under the [CDDL or GPL + * Version 2] license." If you don't indicate a single choice of license, a + * recipient has the option to distribute your version of this file under + * either the CDDL, the GPL Version 2 or to extend the choice of license to + * its licensees as provided above. However, if you add GPL Version 2 code + * and therefore, elected the GPL Version 2 license, then the option applies + * only if the new code is made subject to such option by the copyright + * holder. + */ package fish.payara.microprofile.faulttolerance.test; import static org.junit.Assert.assertEquals; From 49b70ea5c8161a0b023e9b78de942b220e9d3058 Mon Sep 17 00:00:00 2001 From: Jan Bernitt Date: Mon, 22 Apr 2019 16:58:38 +0200 Subject: [PATCH 18/30] PAYARA-3468 added test for configuration override priorities --- .../service/ConfigOverridePriorityTest.java | 124 +++++++++++++++++- .../service/ConfigOverrideTest.java | 12 +- .../faulttolerance/test/ConfigOverrides.java | 22 +++- 3 files changed, 147 insertions(+), 11 deletions(-) diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/service/ConfigOverridePriorityTest.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/service/ConfigOverridePriorityTest.java index 7326282e80c..945d76d34f4 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/service/ConfigOverridePriorityTest.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/service/ConfigOverridePriorityTest.java @@ -39,9 +39,129 @@ */ package fish.payara.microprofile.faulttolerance.service; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.lang.reflect.Method; +import java.time.temporal.ChronoUnit; + +import org.eclipse.microprofile.faulttolerance.Asynchronous; +import org.eclipse.microprofile.faulttolerance.Retry; +import org.eclipse.microprofile.faulttolerance.Timeout; +import org.junit.Test; + +import fish.payara.microprofile.faulttolerance.FaultToleranceConfig; +import fish.payara.microprofile.faulttolerance.policy.StaticAnalysisContext; +import fish.payara.microprofile.faulttolerance.test.ConfigOverrides; +import fish.payara.microprofile.faulttolerance.test.TestUtils; + +/** + * Tests that the priority of different override levels is correct. + * + * @author Jan Bernitt + */ +@Retry(delayUnit = ChronoUnit.MINUTES) public class ConfigOverridePriorityTest { - //TODO override prio test + private ConfigOverrides overrides = new ConfigOverrides(); + private BindableFaultToleranceConfig configFactory = new BindableFaultToleranceConfig(overrides, null); + + @Test + public void onlyMethodLevelAnnotationPresent() { + Method annotatedMethod = TestUtils.getAnnotatedMethod(); + FaultToleranceConfig config = configFactory.bindTo(new StaticAnalysisContext(getClass(), annotatedMethod)); + Timeout timeout = config.getAnnotation(Timeout.class); + assertEquals(42L, config.value(timeout)); + + overrides.override(Timeout.class, "value", 11L); + assertEquals("should be: global override effective", 11L, config.value(timeout)); + + overrides.override(getClass(), Timeout.class, "value", 22L); + assertEquals("should be: class level override ineffective because no class level annotation present", + 11L, config.value(timeout)); + + // do method level override + overrides.override(annotatedMethod, Timeout.class, "value", 33L); + assertEquals("should be: method level override effective", 33L, config.value(timeout)); + } + + @Timeout(value = 42L) + public String onlyMethodLevelAnnotationPresent_Method() { + return "test"; + } + + @Test + public void methodAndClassLevelAnnotationPresent() { + Method annotatedMethod = TestUtils.getAnnotatedMethod(); + FaultToleranceConfig config = configFactory.bindTo(new StaticAnalysisContext(getClass(), annotatedMethod)); + Retry retry = config.getAnnotation(Retry.class); + assertEquals(ChronoUnit.WEEKS, config.delayUnit(retry)); + + overrides.override(Retry.class, "delayUnit", ChronoUnit.ERAS); + assertEquals("should be: global override effective", ChronoUnit.ERAS, config.delayUnit(retry)); + + overrides.override(getClass(), Retry.class, "delayUnit", ChronoUnit.MICROS); + assertEquals("should be: class level annotation present but override ineffective because method level annotation is present", + ChronoUnit.ERAS, config.delayUnit(retry)); + + overrides.override(annotatedMethod, Retry.class, "delayUnit", ChronoUnit.YEARS); + assertEquals("should be: method level override effective", ChronoUnit.YEARS, config.delayUnit(retry)); + } + + @Retry(delayUnit = ChronoUnit.WEEKS) + public String methodAndClassLevelAnnotationPresent_Method() { + return "test"; + } + + @Test + public void onlyClassLevelAnnotationPresent() { + Method annotatedMethod = TestUtils.getAnnotatedMethod(); + FaultToleranceConfig config = configFactory.bindTo(new StaticAnalysisContext(getClass(), annotatedMethod)); + Retry annotation = config.getAnnotation(Retry.class); + assertEquals(ChronoUnit.MINUTES, config.delayUnit(annotation)); + + overrides.override(Retry.class, "delayUnit", ChronoUnit.CENTURIES); + assertEquals("should be: global override effective", ChronoUnit.CENTURIES, config.delayUnit(annotation)); + + overrides.override(getClass(), Retry.class, "delayUnit", ChronoUnit.DAYS); + assertEquals("should be: class level override effective because annotation present on class level", + ChronoUnit.DAYS, config.delayUnit(annotation)); + + overrides.override(annotatedMethod, Retry.class, "delayUnit", ChronoUnit.HOURS); + assertEquals("should be: method level override ineffective because no method level annotation present", + ChronoUnit.DAYS, config.delayUnit(annotation)); + } + + public String onlyClassLevelAnnotationPresent_Method() { + return "test"; + } + + @Test + public void enabledIsIndependentOfPresentAnnotations() { + Method annotatedMethod = TestUtils.getAnnotatedMethod(); + FaultToleranceConfig config = configFactory.bindTo(new StaticAnalysisContext(getClass(), annotatedMethod)); + assertTrue("should be: present annotation is enabled by default", config.isEnabled(Asynchronous.class)); + assertTrue("should be: not present annotation is enabled by default", config.isEnabled(Retry.class)); + + overrides.override(Asynchronous.class, "enabled", false); + overrides.override(Retry.class, "enabled", false); + assertFalse("should be: global override for present annotation has effect", config.isEnabled(Asynchronous.class)); + assertFalse("should be: global override for not present annotation has effect", config.isEnabled(Retry.class)); + + overrides.override(getClass(), Asynchronous.class, "enabled", true); + overrides.override(getClass(), Retry.class, "enabled", true); + assertTrue("should be: class level override for present annotation has effect", config.isEnabled(Asynchronous.class)); + assertTrue("should be: class level override for not present annotation has effect", config.isEnabled(Retry.class)); + + overrides.override(annotatedMethod, Asynchronous.class, "enabled", false); + overrides.override(annotatedMethod, Retry.class, "enabled", false); + assertFalse("should be: method level override for present annotation has effect", config.isEnabled(Asynchronous.class)); + assertFalse("should be: method level override for not present annotation has effect", config.isEnabled(Retry.class)); + } - //TODO enabled override prio test + @Asynchronous + public String enabledIsIndependentOfPresentAnnotations_Method() { + return "test"; + } } diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/service/ConfigOverrideTest.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/service/ConfigOverrideTest.java index e7ec00ac0a8..59315116fa8 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/service/ConfigOverrideTest.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/service/ConfigOverrideTest.java @@ -63,7 +63,7 @@ /** * Tests that properties can be used to override annotation attributes. * - * In response to: + * Also in response to: * * - https://github.com/payara/Payara/issues/3762 * - https://github.com/payara/Payara/issues/3821 @@ -73,7 +73,7 @@ public class ConfigOverrideTest { private ConfigOverrides overrides = new ConfigOverrides(); - private BindableFaultToleranceConfig config = new BindableFaultToleranceConfig(overrides, null); + private BindableFaultToleranceConfig configFactory = new BindableFaultToleranceConfig(overrides, null); /* * Tests @@ -168,15 +168,15 @@ public String retryRetryOnOverride_Method() { private void assertOverridden(Class annotationType, String propertyName, T annotated, T overridden, BiFunction property) { Method annotatedMethod = getAnnotatedMethod(); - A annotation = annotatedMethod.getAnnotation(annotationType); - FaultToleranceConfig boundConfig = config.bindTo(new StaticAnalysisContext(getClass(), annotatedMethod)); + FaultToleranceConfig config = configFactory.bindTo(new StaticAnalysisContext(getClass(), annotatedMethod)); + A annotation = config.getAnnotation(annotationType); // check we get the expected annotated value - T actual = property.apply(boundConfig, annotation); + T actual = property.apply(config, annotation); assertEqualValue(annotated, actual); // make the override overrides.override(annotatedMethod, annotationType, propertyName, overridden); // now check that we get the expected overridden value - actual = property.apply(boundConfig, annotation); + actual = property.apply(config, annotation); assertEqualValue(overridden, actual); } diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/test/ConfigOverrides.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/test/ConfigOverrides.java index ebc8a2624b7..1cf7e833d5f 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/test/ConfigOverrides.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/test/ConfigOverrides.java @@ -61,8 +61,21 @@ public final class ConfigOverrides implements Config { public void override(Method annotatedMethod, Class annotationType, String propertyName, Object value) { - overrides.put(String.format("%s/%s/%s/%s", annotatedMethod.getDeclaringClass().getName(), - annotatedMethod.getName(), annotationType.getSimpleName(), propertyName), toString(value)); + override(value, String.format("%s/%s/%s/%s", annotatedMethod.getDeclaringClass().getName(), + annotatedMethod.getName(), annotationType.getSimpleName(), propertyName)); + } + + public void override(Class target, Class annotationType, String propertyName, + Object value) { + override(value, String.format("%s/%s/%s", target.getName(), annotationType.getSimpleName(), propertyName)); + } + + public void override(Class annotationType, String propertyName, Object value) { + override(value, String.format("%s/%s", annotationType.getSimpleName(), propertyName)); + } + + private void override(Object value, String key) { + overrides.put(key, toString(value)); } private static String toString(Object value) { @@ -99,10 +112,13 @@ public Optional getOptionalValue(String propertyName, Class propertyTy if (propertyType == Long.class) { return (Optional) Optional.of(Long.valueOf(value)); } + if (propertyType == Boolean.class) { + return (Optional) Optional.of(Boolean.valueOf(value)); + } if (propertyType.isEnum()) { return (Optional) Optional.of(enumValue(propertyType, value)); } - throw new UnsupportedOperationException(); + throw new UnsupportedOperationException("value type not supported: " + propertyType.getSimpleName()); } @SuppressWarnings({ "rawtypes", "unchecked" }) From 90b0ae5c907985542a0a32cdd3b6b3f656d90805 Mon Sep 17 00:00:00 2001 From: Jan Bernitt Date: Mon, 22 Apr 2019 17:51:21 +0200 Subject: [PATCH 19/30] PAYARA-3468 added tests for config enabled scope --- .../service/BindableFaultToleranceConfig.java | 4 +- .../service/ConfigScopeTest.java | 83 +++++++++++++++++++ .../faulttolerance/test/ConfigOverrides.java | 10 +-- 3 files changed, 90 insertions(+), 7 deletions(-) create mode 100644 appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/service/ConfigScopeTest.java diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/service/BindableFaultToleranceConfig.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/service/BindableFaultToleranceConfig.java index a0f7144b2d2..8c73acbade7 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/service/BindableFaultToleranceConfig.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/service/BindableFaultToleranceConfig.java @@ -68,8 +68,8 @@ */ final class BindableFaultToleranceConfig implements FaultToleranceConfig { - private static final String NON_FALLBACK_ENABLED_PROPERTY = "MP_Fault_Tolerance_NonFallback_Enabled"; - private static final String METRICS_ENABLED_PROPERTY = "MP_Fault_Tolerance_Metrics_Enabled"; + static final String NON_FALLBACK_ENABLED_PROPERTY = "MP_Fault_Tolerance_NonFallback_Enabled"; + static final String METRICS_ENABLED_PROPERTY = "MP_Fault_Tolerance_Metrics_Enabled"; private static final Logger logger = Logger.getLogger(BindableFaultToleranceConfig.class.getName()); diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/service/ConfigScopeTest.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/service/ConfigScopeTest.java new file mode 100644 index 00000000000..9907dc45289 --- /dev/null +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/service/ConfigScopeTest.java @@ -0,0 +1,83 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright (c) 2019 Payara Foundation and/or its affiliates. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common Development + * and Distribution License("CDDL") (collectively, the "License"). You + * may not use this file except in compliance with the License. You can + * obtain a copy of the License at + * https://github.com/payara/Payara/blob/master/LICENSE.txt + * See the License for the specific + * language governing permissions and limitations under the License. + * + * When distributing the software, include this License Header Notice in each + * file and include the License file at glassfish/legal/LICENSE.txt. + * + * GPL Classpath Exception: + * The Payara Foundation designates this particular file as subject to the "Classpath" + * exception as provided by the Payara Foundation in the GPL Version 2 section of the License + * file that accompanied this code. + * + * Modifications: + * If applicable, add the following below the License Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyright [year] [name of copyright owner]" + * + * Contributor(s): + * If you wish your version of this file to be governed by only the CDDL or + * only the GPL Version 2, indicate your decision by adding "[Contributor] + * elects to include this software in this distribution under the [CDDL or GPL + * Version 2] license." If you don't indicate a single choice of license, a + * recipient has the option to distribute your version of this file under + * either the CDDL, the GPL Version 2 or to extend the choice of license to + * its licensees as provided above. However, if you add GPL Version 2 code + * and therefore, elected the GPL Version 2 license, then the option applies + * only if the new code is made subject to such option by the copyright + * holder. + */ +package fish.payara.microprofile.faulttolerance.service; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +import fish.payara.microprofile.faulttolerance.FaultToleranceConfig; +import fish.payara.microprofile.faulttolerance.test.ConfigOverrides; + +/** + * Checks that {@link FaultToleranceConfig#isMetricsEnabled()} and {@link FaultToleranceConfig#isNonFallbackEnabled()} + * are only resolved once at applciation startup. + * + * @author Jan Bernitt + */ +public class ConfigScopeTest { + + private ConfigOverrides overrides = new ConfigOverrides(); + + @Test + public void isNonFallbackEnabledOnlyResolvedOnce() { + FaultToleranceConfig config = new BindableFaultToleranceConfig(overrides, null); + assertTrue("default should be true", config.isNonFallbackEnabled()); + + overrides.override(BindableFaultToleranceConfig.NON_FALLBACK_ENABLED_PROPERTY, false); + assertTrue("should still be true since it has been resolved already", config.isNonFallbackEnabled()); + + assertFalse("should be false since property was set before it was resolved", + new BindableFaultToleranceConfig(overrides, null).isNonFallbackEnabled()); + } + + @Test + public void isMetricsEnabledOnlyResolvedOnce() { + FaultToleranceConfig config = new BindableFaultToleranceConfig(overrides, null); + assertTrue("default should be true", config.isMetricsEnabled()); + + overrides.override(BindableFaultToleranceConfig.METRICS_ENABLED_PROPERTY, false); + assertTrue("should still be true since it has been resolved already", config.isMetricsEnabled()); + + assertFalse("should be false since property was set before it was resolved", + new BindableFaultToleranceConfig(overrides, null).isMetricsEnabled()); + } +} diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/test/ConfigOverrides.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/test/ConfigOverrides.java index 1cf7e833d5f..62b04661d70 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/test/ConfigOverrides.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/test/ConfigOverrides.java @@ -61,20 +61,20 @@ public final class ConfigOverrides implements Config { public void override(Method annotatedMethod, Class annotationType, String propertyName, Object value) { - override(value, String.format("%s/%s/%s/%s", annotatedMethod.getDeclaringClass().getName(), - annotatedMethod.getName(), annotationType.getSimpleName(), propertyName)); + override(String.format("%s/%s/%s/%s", annotatedMethod.getDeclaringClass().getName(), + annotatedMethod.getName(), annotationType.getSimpleName(), propertyName), value); } public void override(Class target, Class annotationType, String propertyName, Object value) { - override(value, String.format("%s/%s/%s", target.getName(), annotationType.getSimpleName(), propertyName)); + override(String.format("%s/%s/%s", target.getName(), annotationType.getSimpleName(), propertyName), value); } public void override(Class annotationType, String propertyName, Object value) { - override(value, String.format("%s/%s", annotationType.getSimpleName(), propertyName)); + override(String.format("%s/%s", annotationType.getSimpleName(), propertyName), value); } - private void override(Object value, String key) { + public void override(String key, Object value) { overrides.put(key, toString(value)); } From 24ca2e998ffd40c8e453b6aedc47cb7d714b78f0 Mon Sep 17 00:00:00 2001 From: Jan Bernitt Date: Tue, 23 Apr 2019 12:31:05 +0200 Subject: [PATCH 20/30] PAYARA-3468 fixed: async Future exception handling; added async exception handling test --- .../policy/FaultTolerancePolicy.java | 7 +- .../policy/StaticAnalysisContext.java | 16 +- .../AsyncronousExceptionHandlingTest.java | 127 ++++++++++++++++ .../policy/sub/FallbackMethodBeanC.java | 39 +++++ .../test/FaultToleranceServiceStub.java | 139 ++++++++++++++++++ 5 files changed, 325 insertions(+), 3 deletions(-) create mode 100644 appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/policy/AsyncronousExceptionHandlingTest.java create mode 100644 appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/test/FaultToleranceServiceStub.java diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/FaultTolerancePolicy.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/FaultTolerancePolicy.java index ff281ff4aec..9f46aed48c1 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/FaultTolerancePolicy.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/FaultTolerancePolicy.java @@ -386,7 +386,7 @@ private Object processRetryStage(FaultToleranceInvocation invocation) throws Exc throw new FaultToleranceException("Retry failed"); } - private Object processRetryAsync(FaultToleranceInvocation invocation) throws Exception { + private CompletableFuture processRetryAsync(FaultToleranceInvocation invocation) throws Exception { CompletableFuture asyncAttempt = new CompletableFuture<>(); invocation.service.runAsynchronous(asyncAttempt, invocation.context, () -> invocation.runStageWithWorker(() -> processCircuitBreakerStage(invocation, asyncAttempt))); @@ -398,7 +398,9 @@ private Object processRetryAsync(FaultToleranceInvocation invocation) throws Exc if (ex.getCause() instanceof ExecutionException) { // this cause ExecutionException is caused by annotated method returned a Future that completed exceptionally if (asynchronous.isSuccessWhenCompletedExceptionally()) { - return new CompletableFuture<>().completeExceptionally(ex.getCause()); // only unwrap level added by async processing + CompletableFuture exceptionalResult = new CompletableFuture<>(); + exceptionalResult.completeExceptionally(ex.getCause().getCause()); // unwrap + return exceptionalResult; } throw (Exception) ex.getCause().getCause(); // for retry handling return plain cause } @@ -580,6 +582,7 @@ private Object processBulkheadStage(FaultToleranceInvocation invocation) throws try { return proceed(invocation); } finally { + invocation.metrics.addBulkheadExecutionDuration(System.nanoTime() - executionStartTime); concurrentExecutions.release(); } } diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/StaticAnalysisContext.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/StaticAnalysisContext.java index fbddc21a85f..64f5b06879d 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/StaticAnalysisContext.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/StaticAnalysisContext.java @@ -54,11 +54,22 @@ public final class StaticAnalysisContext implements InvocationContext { private final Class targetClass; private final Method annotated; + private final Object[] arguments; private transient Object target; public StaticAnalysisContext(Class targetClass, Method annotated) { + this(null, targetClass, annotated); + } + + public StaticAnalysisContext(Object target, Method annotated, Object... arguments) { + this(target, target.getClass(), annotated, arguments); + } + + private StaticAnalysisContext(Object target, Class targetClass, Method annotated, Object... arguments) { + this.target = target; this.targetClass = targetClass; this.annotated = annotated; + this.arguments = arguments; } @Override @@ -108,7 +119,10 @@ public Map getContextData() { @Override public Object proceed() throws Exception { - throw new UnsupportedOperationException(); + if (arguments.length != annotated.getParameterCount()) { + throw new UnsupportedOperationException(); + } + return annotated.invoke(getTarget(), arguments); } } diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/policy/AsyncronousExceptionHandlingTest.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/policy/AsyncronousExceptionHandlingTest.java new file mode 100644 index 00000000000..df988a59983 --- /dev/null +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/policy/AsyncronousExceptionHandlingTest.java @@ -0,0 +1,127 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright (c) 2019 Payara Foundation and/or its affiliates. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common Development + * and Distribution License("CDDL") (collectively, the "License"). You + * may not use this file except in compliance with the License. You can + * obtain a copy of the License at + * https://github.com/payara/Payara/blob/master/LICENSE.txt + * See the License for the specific + * language governing permissions and limitations under the License. + * + * When distributing the software, include this License Header Notice in each + * file and include the License file at glassfish/legal/LICENSE.txt. + * + * GPL Classpath Exception: + * The Payara Foundation designates this particular file as subject to the "Classpath" + * exception as provided by the Payara Foundation in the GPL Version 2 section of the License + * file that accompanied this code. + * + * Modifications: + * If applicable, add the following below the License Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyright [year] [name of copyright owner]" + * + * Contributor(s): + * If you wish your version of this file to be governed by only the CDDL or + * only the GPL Version 2, indicate your decision by adding "[Contributor] + * elects to include this software in this distribution under the [CDDL or GPL + * Version 2] license." If you don't indicate a single choice of license, a + * recipient has the option to distribute your version of this file under + * either the CDDL, the GPL Version 2 or to extend the choice of license to + * its licensees as provided above. However, if you add GPL Version 2 code + * and therefore, elected the GPL Version 2 license, then the option applies + * only if the new code is made subject to such option by the copyright + * holder. + */ +package fish.payara.microprofile.faulttolerance.policy; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.lang.reflect.Method; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.atomic.AtomicInteger; + +import org.eclipse.microprofile.faulttolerance.Asynchronous; +import org.eclipse.microprofile.faulttolerance.Retry; +import org.junit.Test; + +import fish.payara.microprofile.faulttolerance.test.FaultToleranceServiceStub; +import fish.payara.microprofile.faulttolerance.test.TestUtils; + +/** + * Checks that {@link Asynchronous} methods exception handling use proper semantics based on the return type of the + * annotated method. + * + * A {@link CompletionStage} is only successful if it is completed successful (that is with a value). + * In contrast to {@link CompletionStage} a method returning a {@link Future} is successful if it returns a + * {@link Future} instance independent of if that {@link Future} later completes with an exception. + * + * @author Jan Bernitt + */ +public class AsyncronousExceptionHandlingTest { + + final AtomicInteger asyncFutureWithRetryCallCount = new AtomicInteger(); + final AtomicInteger asyncCompletionStageWithRetryCallCount = new AtomicInteger(); + + @Test + public void asyncFutureWithRetry() throws Exception { + Method annotatedMethod = TestUtils.getAnnotatedMethod(); + FaultTolerancePolicy policy = FaultTolerancePolicy.asAnnotated(getClass(), annotatedMethod); + Future result = AsynchronousPolicy.toFuture( + policy.proceed(new StaticAnalysisContext(this, annotatedMethod), new FaultToleranceServiceStub())); + assertTrue(result.isDone()); + assertEquals("no retry should have happend", 1, asyncFutureWithRetryCallCount.get()); + assertOriginalException(result); + } + + @Retry(jitter = 0L) + @Asynchronous + public Future asyncFutureWithRetry_Method() { + asyncFutureWithRetryCallCount.incrementAndGet(); + return completedExceptionally(new IllegalStateException("Original exception")); + } + + @Test + public void asyncCompletionStageWithRetry() throws Exception { + Method annotatedMethod = TestUtils.getAnnotatedMethod(); + FaultTolerancePolicy policy = FaultTolerancePolicy.asAnnotated(getClass(), annotatedMethod); + Future result = AsynchronousPolicy.toFuture( + policy.proceed(new StaticAnalysisContext(this, annotatedMethod), new FaultToleranceServiceStub())); + assertTrue(result.isDone()); + assertEquals("retry should have happend", 3, asyncCompletionStageWithRetryCallCount.get()); + assertOriginalException(result); + } + + @Retry(maxRetries = 2, jitter = 0L) + @Asynchronous + public CompletionStage asyncCompletionStageWithRetry_Method() { + asyncCompletionStageWithRetryCallCount.incrementAndGet(); + return completedExceptionally(new IllegalStateException("Original exception")); + } + + private static void assertOriginalException(Future result) throws InterruptedException { + try { + result.get(); + fail("Should have completed exceptionally"); + } catch (ExecutionException ex) { + assertEquals("Did not preseve the exception the returned future was completed with", + IllegalStateException.class, ex.getCause().getClass()); + assertEquals("Original exception", ex.getCause().getMessage()); + } + } + + private static CompletableFuture completedExceptionally(Exception ex) { + CompletableFuture future = new CompletableFuture<>(); + future.completeExceptionally(ex); + return future; + } +} diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/policy/sub/FallbackMethodBeanC.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/policy/sub/FallbackMethodBeanC.java index b0d1596cecb..833a8ae5530 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/policy/sub/FallbackMethodBeanC.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/policy/sub/FallbackMethodBeanC.java @@ -1,3 +1,42 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright (c) 2019 Payara Foundation and/or its affiliates. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common Development + * and Distribution License("CDDL") (collectively, the "License"). You + * may not use this file except in compliance with the License. You can + * obtain a copy of the License at + * https://github.com/payara/Payara/blob/master/LICENSE.txt + * See the License for the specific + * language governing permissions and limitations under the License. + * + * When distributing the software, include this License Header Notice in each + * file and include the License file at glassfish/legal/LICENSE.txt. + * + * GPL Classpath Exception: + * The Payara Foundation designates this particular file as subject to the "Classpath" + * exception as provided by the Payara Foundation in the GPL Version 2 section of the License + * file that accompanied this code. + * + * Modifications: + * If applicable, add the following below the License Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyright [year] [name of copyright owner]" + * + * Contributor(s): + * If you wish your version of this file to be governed by only the CDDL or + * only the GPL Version 2, indicate your decision by adding "[Contributor] + * elects to include this software in this distribution under the [CDDL or GPL + * Version 2] license." If you don't indicate a single choice of license, a + * recipient has the option to distribute your version of this file under + * either the CDDL, the GPL Version 2 or to extend the choice of license to + * its licensees as provided above. However, if you add GPL Version 2 code + * and therefore, elected the GPL Version 2 license, then the option applies + * only if the new code is made subject to such option by the copyright + * holder. + */ package fish.payara.microprofile.faulttolerance.policy.sub; @SuppressWarnings("unused") diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/test/FaultToleranceServiceStub.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/test/FaultToleranceServiceStub.java new file mode 100644 index 00000000000..18471b8baec --- /dev/null +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/test/FaultToleranceServiceStub.java @@ -0,0 +1,139 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright (c) 2019 Payara Foundation and/or its affiliates. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common Development + * and Distribution License("CDDL") (collectively, the "License"). You + * may not use this file except in compliance with the License. You can + * obtain a copy of the License at + * https://github.com/payara/Payara/blob/master/LICENSE.txt + * See the License for the specific + * language governing permissions and limitations under the License. + * + * When distributing the software, include this License Header Notice in each + * file and include the License file at glassfish/legal/LICENSE.txt. + * + * GPL Classpath Exception: + * The Payara Foundation designates this particular file as subject to the "Classpath" + * exception as provided by the Payara Foundation in the GPL Version 2 section of the License + * file that accompanied this code. + * + * Modifications: + * If applicable, add the following below the License Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyright [year] [name of copyright owner]" + * + * Contributor(s): + * If you wish your version of this file to be governed by only the CDDL or + * only the GPL Version 2, indicate your decision by adding "[Contributor] + * elects to include this software in this distribution under the [CDDL or GPL + * Version 2] license." If you don't indicate a single choice of license, a + * recipient has the option to distribute your version of this file under + * either the CDDL, the GPL Version 2 or to extend the choice of license to + * its licensees as provided above. However, if you add GPL Version 2 code + * and therefore, elected the GPL Version 2 license, then the option applies + * only if the new code is made subject to such option by the copyright + * holder. + */ +package fish.payara.microprofile.faulttolerance.test; + +import java.lang.reflect.Method; +import java.util.concurrent.Callable; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Future; +import java.util.concurrent.RejectedExecutionException; + +import javax.interceptor.InvocationContext; + +import org.eclipse.microprofile.faulttolerance.FallbackHandler; + +import fish.payara.microprofile.faulttolerance.FaultToleranceConfig; +import fish.payara.microprofile.faulttolerance.FaultToleranceMetrics; +import fish.payara.microprofile.faulttolerance.FaultToleranceService; +import fish.payara.microprofile.faulttolerance.policy.AsynchronousPolicy; +import fish.payara.microprofile.faulttolerance.service.Stereotypes; +import fish.payara.microprofile.faulttolerance.state.BulkheadSemaphore; +import fish.payara.microprofile.faulttolerance.state.CircuitBreakerState; + +/** + * A stub of {@link FaultToleranceService} that can be used in tests as a basis. + * + * Most methods need to be overridden with test behaviour. + * + * The {@link #runAsynchronous(CompletableFuture, InvocationContext, Callable)} does run the task synchronous for + * deterministic tests behaviour. + * + * @author Jan Bernitt + * + */ +public class FaultToleranceServiceStub implements FaultToleranceService { + + @Override + public FaultToleranceConfig getConfig(InvocationContext context, Stereotypes stereotypes) { + return FaultToleranceConfig.asAnnotated(context.getTarget().getClass(), context.getMethod()); + } + + @Override + public FaultToleranceMetrics getMetrics(InvocationContext context) { + return FaultToleranceMetrics.DISABLED; + } + + @Override + public CircuitBreakerState getState(int requestVolumeThreshold, InvocationContext context) { + throw new UnsupportedOperationException("Override for test case"); + } + + @Override + public BulkheadSemaphore getConcurrentExecutions(int maxConcurrentThreads, InvocationContext context) { + throw new UnsupportedOperationException("Override for test case"); + } + + @Override + public BulkheadSemaphore getWaitingQueuePopulation(int queueCapacity, InvocationContext context) { + throw new UnsupportedOperationException("Override for test case"); + } + + @Override + public void delay(long delayMillis, InvocationContext context) throws InterruptedException { + throw new UnsupportedOperationException("Override for test case"); + } + + @Override + public Future scheduleDelayed(long delayMillis, Runnable task) throws Exception { + throw new UnsupportedOperationException("Override for test case"); + } + + @Override + public void runAsynchronous(CompletableFuture asyncResult, InvocationContext context, Callable task) + throws RejectedExecutionException { + try { + asyncResult.complete(AsynchronousPolicy.toFuture(task.call()).get()); + } catch (Exception e) { + asyncResult.completeExceptionally(e); + } + } + + @Override + public Object fallbackHandle(Class> fallbackClass, InvocationContext context, + Exception ex) throws Exception { + throw new UnsupportedOperationException("Override for test case"); + } + + @Override + public Object fallbackInvoke(Method fallbackMethod, InvocationContext context) throws Exception { + throw new UnsupportedOperationException("Override for test case"); + } + + @Override + public void trace(String method, InvocationContext context) { + //NOOP, tracing not supported + } + + @Override + public void endTrace() { + //NOOP, tracing not supported + } + +} From 1f461c147e4eb8a3f9bbbd929b48bf613b78a81d Mon Sep 17 00:00:00 2001 From: Jan Bernitt Date: Tue, 23 Apr 2019 14:35:23 +0200 Subject: [PATCH 21/30] PAYARA-3468 added more tests for async error handling --- .../policy/FaultTolerancePolicy.java | 35 +++---- .../policy/StaticAnalysisContext.java | 7 +- .../AsyncronousExceptionHandlingTest.java | 91 ++++++++++++++++--- .../faulttolerance/test/TestUtils.java | 16 +++- 4 files changed, 116 insertions(+), 33 deletions(-) diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/FaultTolerancePolicy.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/FaultTolerancePolicy.java index 9f46aed48c1..99b79481eda 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/FaultTolerancePolicy.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/FaultTolerancePolicy.java @@ -541,20 +541,15 @@ private Object processBulkheadStage(FaultToleranceInvocation invocation) throws invocation.metrics.linkBulkheadWaitingQueuePopulation(waitingQueuePopulation::acquiredPermits); } } - long executionStartTime = System.nanoTime(); logger.log(Level.FINER, "Attempting to acquire bulkhead execution permit."); if (concurrentExecutions.tryAcquireFair()) { logger.log(Level.FINE, "Acquired bulkhead execution permit."); invocation.metrics.incrementBulkheadCallsAcceptedTotal(); if (isAsynchronous()) { - invocation.metrics.addBulkheadWaitingDuration(0L); // we did not wait but need to factor in the invocation for histogram quartiles - } - try { - return proceed(invocation); - } finally { - invocation.metrics.addBulkheadExecutionDuration(System.nanoTime() - executionStartTime); - concurrentExecutions.release(); + // we did not wait but need to factor in the invocation for histogram quartiles + invocation.metrics.addBulkheadWaitingDuration(1L); // using 1ns because 0 leads to flaky test } + return processBulkheadExecution(invocation, concurrentExecutions); } if (waitingQueuePopulation == null) { // plain semaphore style, fail: invocation.metrics.incrementBulkheadCallsRejectedTotal(); @@ -565,10 +560,11 @@ private Object processBulkheadStage(FaultToleranceInvocation invocation) throws if (waitingQueuePopulation.tryAcquireFair()) { logger.log(Level.FINE, "Acquired bulkhead queue permit."); invocation.metrics.incrementBulkheadCallsAcceptedTotal(); - long queueStartTime = System.nanoTime(); + long waitingSince = System.nanoTime(); try { invocation.trace("obtainBulkheadSemaphore"); concurrentExecutions.acquire(); // block until execution permit becomes available + waitingQueuePopulation.release(); } catch (InterruptedException ex) { logger.log(Level.FINE, "Interrupted acquiring bulkhead permit", ex); invocation.metrics.incrementBulkheadCallsRejectedTotal(); @@ -576,20 +572,25 @@ private Object processBulkheadStage(FaultToleranceInvocation invocation) throws throw new BulkheadException(ex); } finally { invocation.endTrace(); - invocation.metrics.addBulkheadWaitingDuration(System.nanoTime() - queueStartTime); - } - waitingQueuePopulation.release(); - try { - return proceed(invocation); - } finally { - invocation.metrics.addBulkheadExecutionDuration(System.nanoTime() - executionStartTime); - concurrentExecutions.release(); + invocation.metrics.addBulkheadWaitingDuration(System.nanoTime() - waitingSince); } + return processBulkheadExecution(invocation, concurrentExecutions); } invocation.metrics.incrementBulkheadCallsRejectedTotal(); throw new BulkheadException("No free work or queue permits."); } + private static Object processBulkheadExecution(FaultToleranceInvocation invocation, BulkheadSemaphore concurrentExecutions) + throws Exception { + long executionSince = System.nanoTime(); + try { + return proceed(invocation); + } finally { + invocation.metrics.addBulkheadExecutionDuration(System.nanoTime() - executionSince); + concurrentExecutions.release(); + } + } + /** * Final stage where the actual wrapped method call occurs. */ diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/StaticAnalysisContext.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/StaticAnalysisContext.java index 64f5b06879d..ab202e1fa93 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/StaticAnalysisContext.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/StaticAnalysisContext.java @@ -40,6 +40,7 @@ package fish.payara.microprofile.faulttolerance.policy; import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Map; @@ -122,7 +123,11 @@ public Object proceed() throws Exception { if (arguments.length != annotated.getParameterCount()) { throw new UnsupportedOperationException(); } - return annotated.invoke(getTarget(), arguments); + try { + return annotated.invoke(getTarget(), arguments); + } catch (InvocationTargetException ex) { + throw (Exception) ex.getTargetException(); + } } } diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/policy/AsyncronousExceptionHandlingTest.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/policy/AsyncronousExceptionHandlingTest.java index df988a59983..b48f94c9454 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/policy/AsyncronousExceptionHandlingTest.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/policy/AsyncronousExceptionHandlingTest.java @@ -70,16 +70,20 @@ public class AsyncronousExceptionHandlingTest { final AtomicInteger asyncFutureWithRetryCallCount = new AtomicInteger(); + final AtomicInteger asyncFutureWithRetryThrowsExceptionCallCount = new AtomicInteger(); + final AtomicInteger asyncCompletionStageWithRetryCallCount = new AtomicInteger(); + final AtomicInteger asyncCompletionStageWithRetryThrowsExceptionCallCount = new AtomicInteger(); + final AtomicInteger asyncCompletionStageWithRetrySuccessOnRetryCallCount = new AtomicInteger(); + /** + * When returning a {@link Future} there is only 1 attempt made since a {@link Future} instance is returned on first + * attempt. + */ @Test public void asyncFutureWithRetry() throws Exception { - Method annotatedMethod = TestUtils.getAnnotatedMethod(); - FaultTolerancePolicy policy = FaultTolerancePolicy.asAnnotated(getClass(), annotatedMethod); - Future result = AsynchronousPolicy.toFuture( - policy.proceed(new StaticAnalysisContext(this, annotatedMethod), new FaultToleranceServiceStub())); - assertTrue(result.isDone()); - assertEquals("no retry should have happend", 1, asyncFutureWithRetryCallCount.get()); + Future result = proceedToDoneFuture(); + assertEquals("no retry attempt should occur", 1, asyncFutureWithRetryCallCount.get()); assertOriginalException(result); } @@ -90,14 +94,32 @@ public Future asyncFutureWithRetry_Method() { return completedExceptionally(new IllegalStateException("Original exception")); } + /** + * When the method returning a {@link Future} throws an {@link Exception} the {@link Retry} is applied and further + * attempts are made. + */ + @Test + public void asyncFutureWithRetryThrowsException() throws Exception { + Future result = proceedToDoneFuture(); + assertEquals("all retry attempts should occur", 4, asyncFutureWithRetryThrowsExceptionCallCount.get()); + assertOriginalException(result); + } + + @Retry(jitter = 0L) + @Asynchronous + public Future asyncFutureWithRetryThrowsException_Method() { + asyncFutureWithRetryThrowsExceptionCallCount.incrementAndGet(); + throw new IllegalStateException("Originally thrown exception"); + } + + /** + * A method returning {@link CompletionStage} must complete successful otherwise {@link Retry} is effective and + * further attempts are made. + */ @Test public void asyncCompletionStageWithRetry() throws Exception { - Method annotatedMethod = TestUtils.getAnnotatedMethod(); - FaultTolerancePolicy policy = FaultTolerancePolicy.asAnnotated(getClass(), annotatedMethod); - Future result = AsynchronousPolicy.toFuture( - policy.proceed(new StaticAnalysisContext(this, annotatedMethod), new FaultToleranceServiceStub())); - assertTrue(result.isDone()); - assertEquals("retry should have happend", 3, asyncCompletionStageWithRetryCallCount.get()); + Future result = proceedToDoneFuture(); + assertEquals("all retry attempts should occur", 3, asyncCompletionStageWithRetryCallCount.get()); assertOriginalException(result); } @@ -108,6 +130,49 @@ public CompletionStage asyncCompletionStageWithRetry_Method() { return completedExceptionally(new IllegalStateException("Original exception")); } + @Test + public void asyncCompletionStageWithRetryThrowsException() throws Exception { + Future result = proceedToDoneFuture(); + assertEquals("all retry attempts should occur", 3, asyncCompletionStageWithRetryThrowsExceptionCallCount.get()); + assertOriginalException(result); + } + + @Retry(maxRetries = 2, jitter = 0L) + @Asynchronous + public CompletionStage asyncCompletionStageWithRetryThrowsException_Method() { + asyncCompletionStageWithRetryThrowsExceptionCallCount.incrementAndGet(); + throw new IllegalStateException("Originally thrown exception"); + } + + /** + * A {@link CompletionStage} that first completes exceptionally should make further {@link Retry} attempts and + * complete successful in this scenario. + */ + @Test + public void asyncCompletionStageWithRetrySuccessOnRetry() throws Exception { + Future result = proceedToDoneFuture(); + assertEquals("one retry should lead to success", 2, asyncCompletionStageWithRetrySuccessOnRetryCallCount.get()); + assertEquals("Success", result.get()); + } + + @Retry(maxRetries = 2, jitter = 0L) + @Asynchronous + public CompletionStage asyncCompletionStageWithRetrySuccessOnRetry_Method() { + int callNo = asyncCompletionStageWithRetrySuccessOnRetryCallCount.incrementAndGet(); + return callNo < 2 + ? completedExceptionally(new IllegalStateException("Original exception")) + : CompletableFuture.completedFuture("Success"); + } + + private Future proceedToDoneFuture() throws Exception { + Method annotatedMethod = TestUtils.getAnnotatedMethod(); + FaultTolerancePolicy policy = FaultTolerancePolicy.asAnnotated(getClass(), annotatedMethod); + Future result = AsynchronousPolicy.toFuture( + policy.proceed(new StaticAnalysisContext(this, annotatedMethod), new FaultToleranceServiceStub())); + assertTrue(result.isDone()); + return result; + } + private static void assertOriginalException(Future result) throws InterruptedException { try { result.get(); @@ -115,7 +180,7 @@ private static void assertOriginalException(Future result) throws Interrupted } catch (ExecutionException ex) { assertEquals("Did not preseve the exception the returned future was completed with", IllegalStateException.class, ex.getCause().getClass()); - assertEquals("Original exception", ex.getCause().getMessage()); + assertTrue(ex.getCause().getMessage().startsWith("Original")); } } diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/test/TestUtils.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/test/TestUtils.java index 09f2b6c02d8..672b38894d4 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/test/TestUtils.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/test/TestUtils.java @@ -92,8 +92,7 @@ public static void assertAnnotationInvalid(String expectedErrorMessage) { public static Method getAnnotatedMethod() { StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace(); int i = 0; - while (!stackTraceElements[i].getClassName().endsWith("Test") - || stackTraceElements[i].getMethodName().startsWith("assert")) { + while (!isTestMethod(stackTraceElements[i])) { i++; } StackTraceElement testMethodElement = stackTraceElements[i]; @@ -104,4 +103,17 @@ public static Method getAnnotatedMethod() { throw new AssertionError("Failed to find annotated method in test class: ", e); } } + + private static boolean isTestMethod(StackTraceElement element) { + if (!element.getClassName().endsWith("Test")) { + return false; + } + try { + Class testClass = Class.forName(element.getClassName()); + Method elementMethod = testClass.getMethod(element.getMethodName()); + return elementMethod.isAnnotationPresent(org.junit.Test.class); + } catch (Exception e) { + return false; + } + } } From fdec8f7629e03caefb57e4314e19352abefca915 Mon Sep 17 00:00:00 2001 From: Jan Bernitt Date: Tue, 23 Apr 2019 15:30:06 +0200 Subject: [PATCH 22/30] PAYARA-3468 added javadoc --- .../policy/AsyncronousExceptionHandlingTest.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/policy/AsyncronousExceptionHandlingTest.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/policy/AsyncronousExceptionHandlingTest.java index b48f94c9454..ac3355dcb3a 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/policy/AsyncronousExceptionHandlingTest.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/policy/AsyncronousExceptionHandlingTest.java @@ -130,6 +130,10 @@ public CompletionStage asyncCompletionStageWithRetry_Method() { return completedExceptionally(new IllegalStateException("Original exception")); } + /** + * For a method returning {@link CompletionStage} throwing an exception is similarly handled to returning a value + * that later completes exceptionally. Therefore retry attempts should occur. + */ @Test public void asyncCompletionStageWithRetryThrowsException() throws Exception { Future result = proceedToDoneFuture(); From 013c0e6c718c5756a868e8546b084cf245566813 Mon Sep 17 00:00:00 2001 From: Jan Bernitt Date: Wed, 24 Apr 2019 17:16:50 +0200 Subject: [PATCH 23/30] PAYARA-3468 added tests for fallback basic behaviour --- .../policy/StaticAnalysisContext.java | 6 +- .../AsyncronousExceptionHandlingTest.java | 2 +- .../policy/FallbackBasicTest.java | 172 ++++++++++++++++++ .../FaultToleranceServiceStub.java | 16 +- 4 files changed, 189 insertions(+), 7 deletions(-) create mode 100644 appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/policy/FallbackBasicTest.java rename appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/{test => service}/FaultToleranceServiceStub.java (87%) diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/StaticAnalysisContext.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/StaticAnalysisContext.java index ab202e1fa93..46e1f55cb49 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/StaticAnalysisContext.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/StaticAnalysisContext.java @@ -55,7 +55,7 @@ public final class StaticAnalysisContext implements InvocationContext { private final Class targetClass; private final Method annotated; - private final Object[] arguments; + private Object[] arguments; private transient Object target; public StaticAnalysisContext(Class targetClass, Method annotated) { @@ -105,12 +105,12 @@ public Constructor getConstructor() { @Override public Object[] getParameters() { - throw new UnsupportedOperationException(); + return arguments; } @Override public void setParameters(Object[] params) { - throw new UnsupportedOperationException(); + this.arguments = params; } @Override diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/policy/AsyncronousExceptionHandlingTest.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/policy/AsyncronousExceptionHandlingTest.java index ac3355dcb3a..c3a2cc82355 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/policy/AsyncronousExceptionHandlingTest.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/policy/AsyncronousExceptionHandlingTest.java @@ -54,7 +54,7 @@ import org.eclipse.microprofile.faulttolerance.Retry; import org.junit.Test; -import fish.payara.microprofile.faulttolerance.test.FaultToleranceServiceStub; +import fish.payara.microprofile.faulttolerance.service.FaultToleranceServiceStub; import fish.payara.microprofile.faulttolerance.test.TestUtils; /** diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/policy/FallbackBasicTest.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/policy/FallbackBasicTest.java new file mode 100644 index 00000000000..c1b86922441 --- /dev/null +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/policy/FallbackBasicTest.java @@ -0,0 +1,172 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright (c) 2019 Payara Foundation and/or its affiliates. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common Development + * and Distribution License("CDDL") (collectively, the "License"). You + * may not use this file except in compliance with the License. You can + * obtain a copy of the License at + * https://github.com/payara/Payara/blob/master/LICENSE.txt + * See the License for the specific + * language governing permissions and limitations under the License. + * + * When distributing the software, include this License Header Notice in each + * file and include the License file at glassfish/legal/LICENSE.txt. + * + * GPL Classpath Exception: + * The Payara Foundation designates this particular file as subject to the "Classpath" + * exception as provided by the Payara Foundation in the GPL Version 2 section of the License + * file that accompanied this code. + * + * Modifications: + * If applicable, add the following below the License Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyright [year] [name of copyright owner]" + * + * Contributor(s): + * If you wish your version of this file to be governed by only the CDDL or + * only the GPL Version 2, indicate your decision by adding "[Contributor] + * elects to include this software in this distribution under the [CDDL or GPL + * Version 2] license." If you don't indicate a single choice of license, a + * recipient has the option to distribute your version of this file under + * either the CDDL, the GPL Version 2 or to extend the choice of license to + * its licensees as provided above. However, if you add GPL Version 2 code + * and therefore, elected the GPL Version 2 license, then the option applies + * only if the new code is made subject to such option by the copyright + * holder. + */ +package fish.payara.microprofile.faulttolerance.policy; + +import static org.junit.Assert.assertEquals; + +import java.lang.reflect.Method; + +import org.eclipse.microprofile.faulttolerance.ExecutionContext; +import org.eclipse.microprofile.faulttolerance.Fallback; +import org.eclipse.microprofile.faulttolerance.FallbackHandler; +import org.junit.Test; + +import fish.payara.microprofile.faulttolerance.service.FaultToleranceServiceStub; +import fish.payara.microprofile.faulttolerance.test.TestUtils; + +/** + * Tests the basic correctness of the {@link Fallback} handling. + * + * @author Jan Bernitt + */ +public class FallbackBasicTest implements FallbackHandler { + + /** + * Most basic case where annotated method fails and the fallback returns a result (no method arguments) + */ + @Test + public void fallbackMethod( ) throws Exception { + assertEquals("fallbackMethod", proceedToResultValue()); + } + + @Fallback(fallbackMethod = "fallbackMethod_FallbackMethod") + public String fallbackMethod_Method() { + throw new RuntimeException("Normal execution failed."); + } + + public String fallbackMethod_FallbackMethod() { + return "fallbackMethod"; + } + + /** + * Annotated method fails and fallback returns a result with method arguments + */ + @Test + public void fallbackMethodWithArguments() throws Exception { + assertEquals("fallbackMethodWithArguments:Peter", proceedToResultValue("Peter")); + } + + @Fallback(fallbackMethod = "fallbackMethodWithArguments_FallbackMethod") + public String fallbackMethodWithArguments_Method(@SuppressWarnings("unused") String name) { + throw new RuntimeException("Normal execution failed."); + } + + public String fallbackMethodWithArguments_FallbackMethod(String name) { + return "fallbackMethodWithArguments:" + name; + } + + /** + * Both annotated method and fallback method fail. + */ + @Test(expected = IllegalStateException.class) + public void fallbackMethodFailsToo() throws Exception { + proceedToResultValue(); + } + + @Fallback(fallbackMethod = "fallbackMethodFailsToo_FallbackMethod") + public void fallbackMethodFailsToo_Method() { + throw new RuntimeException("Normal execution failed."); + } + + public void fallbackMethodFailsToo_FallbackMethod() { + throw new IllegalStateException("Fallback fails as well."); + } + + /** + * Most basic {@link FallbackHandler} test where annotated method fails and fallback returns a result value. + */ + @Test + public void fallbackHandler() throws Exception { + assertEquals("fallbackHandler", proceedToResultValue()); + } + + @Fallback(value = FallbackBasicTest.class) + public String fallbackHandler_Method() { + throw new RuntimeException("Normal execution failed."); + } + + /** + * Basic case of a {@link FallbackHandler} for a method with arguments. + */ + @Test + public void fallbackHandlerWithArguments() throws Exception { + assertEquals("fallbackHandlerWithArguments:Peter", proceedToResultValue("Peter")); + } + + @Fallback(value = FallbackBasicTest.class) + public String fallbackHandlerWithArguments_Method(@SuppressWarnings("unused") String name) { + throw new RuntimeException("Normal execution failed."); + } + + /** + * Both annotated method and fallback handler fail. + */ + @Test(expected = IllegalStateException.class) + public void fallbackHandlerFailsToo() throws Exception { + proceedToResultValue(); + } + + @Fallback(value = FallbackBasicTest.class) + public String fallbackHandlerFailsToo_Method() { + throw new RuntimeException("Normal execution failed."); + } + + /** + * Implements the result for all 3 {@link FallbackHandler} tests. + */ + @Override + public String handle(ExecutionContext context) { + if (context.getMethod().getName().equals("fallbackHandlerFailsToo_Method")) { + throw new IllegalStateException("Handler fails as well"); + } + String resultValue = context.getMethod().getName().replace("_Method", ""); + if (context.getParameters().length > 0) { + resultValue += ":" + context.getParameters()[0]; + } + return resultValue; + } + + private Object proceedToResultValue(Object... methodArguments) throws Exception { + Method annotatedMethod = TestUtils.getAnnotatedMethod(); + FaultTolerancePolicy policy = FaultTolerancePolicy.asAnnotated(getClass(), annotatedMethod); + return policy.proceed(new StaticAnalysisContext(this, annotatedMethod, methodArguments), + new FaultToleranceServiceStub()); + } +} diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/test/FaultToleranceServiceStub.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/service/FaultToleranceServiceStub.java similarity index 87% rename from appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/test/FaultToleranceServiceStub.java rename to appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/service/FaultToleranceServiceStub.java index 18471b8baec..4faef28e54b 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/test/FaultToleranceServiceStub.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/service/FaultToleranceServiceStub.java @@ -37,8 +37,9 @@ * only if the new code is made subject to such option by the copyright * holder. */ -package fish.payara.microprofile.faulttolerance.test; +package fish.payara.microprofile.faulttolerance.service; +import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.concurrent.Callable; import java.util.concurrent.CompletableFuture; @@ -48,6 +49,7 @@ import javax.interceptor.InvocationContext; import org.eclipse.microprofile.faulttolerance.FallbackHandler; +import org.eclipse.microprofile.faulttolerance.exceptions.FaultToleranceDefinitionException; import fish.payara.microprofile.faulttolerance.FaultToleranceConfig; import fish.payara.microprofile.faulttolerance.FaultToleranceMetrics; @@ -118,12 +120,20 @@ public void runAsynchronous(CompletableFuture asyncResult, InvocationCon @Override public Object fallbackHandle(Class> fallbackClass, InvocationContext context, Exception ex) throws Exception { - throw new UnsupportedOperationException("Override for test case"); + return fallbackClass.newInstance() + .handle(new FaultToleranceExecutionContext(context.getMethod(), context.getParameters(), ex)); } @Override public Object fallbackInvoke(Method fallbackMethod, InvocationContext context) throws Exception { - throw new UnsupportedOperationException("Override for test case"); + try { + fallbackMethod.setAccessible(true); + return fallbackMethod.invoke(context.getTarget(), context.getParameters()); + } catch (InvocationTargetException e) { + throw (Exception) e.getTargetException(); + } catch (IllegalAccessException e) { + throw new FaultToleranceDefinitionException(e); // should not happen as we validated + } } @Override From 039b47116992a562fb8312e893ab785d6f7a6123 Mon Sep 17 00:00:00 2001 From: Jan Bernitt Date: Thu, 25 Apr 2019 10:07:27 +0200 Subject: [PATCH 24/30] PAYARA-3468 added basic correctness test for Bulkhead --- .../policy/BulkheadBasicTest.java | 241 ++++++++++++++++++ 1 file changed, 241 insertions(+) create mode 100644 appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/policy/BulkheadBasicTest.java diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/policy/BulkheadBasicTest.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/policy/BulkheadBasicTest.java new file mode 100644 index 00000000000..6175abd8210 --- /dev/null +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/policy/BulkheadBasicTest.java @@ -0,0 +1,241 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright (c) 2019 Payara Foundation and/or its affiliates. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common Development + * and Distribution License("CDDL") (collectively, the "License"). You + * may not use this file except in compliance with the License. You can + * obtain a copy of the License at + * https://github.com/payara/Payara/blob/master/LICENSE.txt + * See the License for the specific + * language governing permissions and limitations under the License. + * + * When distributing the software, include this License Header Notice in each + * file and include the License file at glassfish/legal/LICENSE.txt. + * + * GPL Classpath Exception: + * The Payara Foundation designates this particular file as subject to the "Classpath" + * exception as provided by the Payara Foundation in the GPL Version 2 section of the License + * file that accompanied this code. + * + * Modifications: + * If applicable, add the following below the License Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyright [year] [name of copyright owner]" + * + * Contributor(s): + * If you wish your version of this file to be governed by only the CDDL or + * only the GPL Version 2, indicate your decision by adding "[Contributor] + * elects to include this software in this distribution under the [CDDL or GPL + * Version 2] license." If you don't indicate a single choice of license, a + * recipient has the option to distribute your version of this file under + * either the CDDL, the GPL Version 2 or to extend the choice of license to + * its licensees as provided above. However, if you add GPL Version 2 code + * and therefore, elected the GPL Version 2 license, then the option applies + * only if the new code is made subject to such option by the copyright + * holder. + */ +package fish.payara.microprofile.faulttolerance.policy; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +import java.lang.reflect.Method; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; + +import javax.interceptor.InvocationContext; + +import org.eclipse.microprofile.faulttolerance.Asynchronous; +import org.eclipse.microprofile.faulttolerance.Bulkhead; +import org.eclipse.microprofile.faulttolerance.exceptions.BulkheadException; +import org.junit.Before; +import org.junit.Test; + +import fish.payara.microprofile.faulttolerance.FaultToleranceService; +import fish.payara.microprofile.faulttolerance.service.FaultToleranceServiceStub; +import fish.payara.microprofile.faulttolerance.state.BulkheadSemaphore; +import fish.payara.microprofile.faulttolerance.test.TestUtils; + +/** + * Tests the basic correctness of {@link Bulkhead} handling. + * + * @author Jan Bernitt + * + */ +public class BulkheadBasicTest { + + final AtomicReference concurrentExecutions = new AtomicReference<>(); + final AtomicReference waitingQueuePopulation = new AtomicReference<>(); + final AtomicInteger bulkheadCallCount = new AtomicInteger(); + + private final FaultToleranceService service = new FaultToleranceServiceStub() { + @Override + public BulkheadSemaphore getConcurrentExecutions(int maxConcurrentThreads, InvocationContext context) { + return concurrentExecutions.updateAndGet(value -> + value != null ? value : new BulkheadSemaphore(maxConcurrentThreads)); + } + + @Override + public BulkheadSemaphore getWaitingQueuePopulation(int queueCapacity, InvocationContext context) { + return waitingQueuePopulation.updateAndGet(value -> + value != null ? value : new BulkheadSemaphore(queueCapacity)); + } + }; + + @Before + public void resetCounters() { + bulkheadCallCount.set(0); + } + + /** + * Makes 2 concurrent request that should succeed acquiring a bulkhead permit. + * The 3 attempt fails as no queue is in place without {@link Asynchronous}. + * + * Needs a timeout because incorrect implementation could otherwise lead to endless waiting. + */ + @Test(timeout = 500) + public void bulkheadWithoutQueue() throws Exception { + Method annotatedMethod = TestUtils.getAnnotatedMethod(); + CompletableFuture waiter = new CompletableFuture<>(); + Runnable task = () -> proceedToResultValueOrFail(this, annotatedMethod, waiter); + new Thread(task).start(); + new Thread(task).start(); + waitUntilPermitsAquired(2, 0); + assertProceedingThrowsBulkheadException(annotatedMethod); + waiter.complete(null); + waitUntilPermitsAquired(0, 0); + assertEquals(2, bulkheadCallCount.get()); + } + + @Bulkhead(value = 2) + public String bulkheadWithoutQueue_Method(Future waiter) { + bulkheadCallCount.incrementAndGet(); + try { + waiter.get(); + } catch (Exception e) { + throw new AssertionError(e); + } + return "Success"; + } + + /** + * First two request can acquire a bulkhead permit. + * Following two request can acquire a queue permit. + * Fifth request fails. + * + * Needs a timeout because incorrect implementation could otherwise lead to endless waiting. + */ + @Test(timeout = 500) + public void bulkheadWithQueue() throws Exception { + Method annotatedMethod = TestUtils.getAnnotatedMethod(); + CompletableFuture waiter = new CompletableFuture<>(); + Runnable task = () -> proceedToResultValueOrFail(this, annotatedMethod, waiter); + new Thread(task).start(); + new Thread(task).start(); + new Thread(task).start(); + new Thread(task).start(); + waitUntilPermitsAquired(2, 2); + assertProceedingThrowsBulkheadException(annotatedMethod); + waiter.complete(null); + waitUntilPermitsAquired(0, 0); + assertEquals(4, bulkheadCallCount.get()); + } + + @Asynchronous + @Bulkhead(value = 2, waitingTaskQueue = 2) + public CompletionStage bulkheadWithQueue_Method(Future waiter) { + bulkheadCallCount.incrementAndGet(); + try { + waiter.get(); + } catch (Exception e) { + throw new AssertionError(e); + } + return CompletableFuture.completedFuture("Success"); + } + + /** + * Similar to {@link #bulkheadWithQueue()} just that we interrupt the queueing threads and expect their permits to + * be released. + */ + @Test(timeout = 500) + public void bulkheadWithQueueInterrupted() throws Exception { + Method annotatedMethod = TestUtils.getAnnotatedMethod(); + CompletableFuture waiter = new CompletableFuture<>(); + Runnable task = () -> proceedToResultValueOrFail(this, annotatedMethod, waiter); + new Thread(task).start(); + new Thread(task).start(); + Thread queueing1 = new Thread(task); + queueing1.start(); + Thread queueing2 = new Thread(task); + queueing2.start(); + waitUntilPermitsAquired(2, 2); + queueing1.interrupt(); + waitUntilPermitsAquired(2, 1); + queueing2.interrupt(); + waitUntilPermitsAquired(2, 0); + waiter.complete(null); + waitUntilPermitsAquired(0, 0); + assertEquals(2, bulkheadCallCount.get()); + } + + @Asynchronous + @Bulkhead(value = 2, waitingTaskQueue = 2) + public CompletionStage bulkheadWithQueueInterrupted_Method(Future waiter) { + return bulkheadWithQueue_Method(waiter); + } + + /* + * Helpers + */ + + private Object proceedToResultValueOrFail(Object test, Method annotatedMethod, Future argument) { + try { + return proceedToResultValue(test, annotatedMethod, argument); + } catch (Exception e) { + throw new AssertionError(e); + } + } + + private Object proceedToResultValue(Object test, Method annotatedMethod, Future argument) throws Exception { + FaultTolerancePolicy policy = FaultTolerancePolicy.asAnnotated(test.getClass(), annotatedMethod); + return policy.proceed(new StaticAnalysisContext(test, annotatedMethod, argument), service); + } + + private void assertProceedingThrowsBulkheadException(Method annotatedMethod) throws Exception { + try { + Object resultValue = proceedToResultValue(this, annotatedMethod, null); + if (resultValue instanceof Future) { + ((Future) resultValue).get(); // should throw the exception + } + fail("Expected to fail with a BulkheadException"); + } catch (BulkheadException ex) { + // as expected for non asyncronous + } catch (ExecutionException ex) { + assertEquals(BulkheadException.class, ex.getCause().getClass()); + } + } + + private void waitUntilPermitsAquired(int concurrentExecutions, int waitingQueuePopulation) { + long delayMs = 4; + while ( !equalAcquiredPermits(concurrentExecutions, this.concurrentExecutions.get()) + || !equalAcquiredPermits(waitingQueuePopulation, this.waitingQueuePopulation.get())) { + try { + Thread.sleep(delayMs); + } catch (InterruptedException e) { + return; // give up (test was cancelled) + } + delayMs *= 2; + } + } + + private static boolean equalAcquiredPermits(int expected, BulkheadSemaphore semaphore) { + return semaphore == null ? expected == 0 : semaphore.acquiredPermits() == expected; + } +} From 6216b29b0978cf835663d391806708d42ceab159 Mon Sep 17 00:00:00 2001 From: Jan Bernitt Date: Thu, 25 Apr 2019 13:11:02 +0200 Subject: [PATCH 25/30] PAYARA-3468 added basic correctness test for circuit breaker --- .../faulttolerance/FaultToleranceService.java | 2 +- .../policy/FaultTolerancePolicy.java | 20 +- .../service/FaultToleranceServiceImpl.java | 2 +- .../policy/BulkheadBasicTest.java | 55 +++-- .../policy/CircuitBreakerBasicTest.java | 213 ++++++++++++++++++ .../service/FaultToleranceServiceStub.java | 2 +- 6 files changed, 255 insertions(+), 39 deletions(-) create mode 100644 appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/policy/CircuitBreakerBasicTest.java diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/FaultToleranceService.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/FaultToleranceService.java index 36867e68e8d..498536ab164 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/FaultToleranceService.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/FaultToleranceService.java @@ -117,7 +117,7 @@ public interface FaultToleranceService { * @param task operation to run * @return A future that can be cancelled if the operation should no longer be run */ - Future scheduleDelayed(long delayMillis, Runnable task) throws Exception; + Future runDelayed(long delayMillis, Runnable task) throws Exception; /** * Runs the task asynchronously and completes the given asyncResult with the its outcome. diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/FaultTolerancePolicy.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/FaultTolerancePolicy.java index 99b79481eda..166f4ff6310 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/FaultTolerancePolicy.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/FaultTolerancePolicy.java @@ -437,9 +437,7 @@ private Object processCircuitBreakerStage(FaultToleranceInvocation invocation, C invocation.metrics.incrementCircuitbreakerCallsFailedTotal(); if (circuitBreaker.failOn(ex)) { logger.log(Level.FINE, "Exception causes CircuitBreaker to transit: half-open => open"); - invocation.metrics.incrementCircuitbreakerOpenedTotal(); - state.open(); - invocation.service.scheduleDelayed(circuitBreaker.delay, state::halfOpen); + openCircuit(invocation, state); } throw ex; } @@ -463,9 +461,7 @@ private Object processCircuitBreakerStage(FaultToleranceInvocation invocation, C } if (state.isOverFailureThreshold(circuitBreaker.requestVolumeThreshold, circuitBreaker.failureRatio)) { logger.log(Level.FINE, "Failure threshold causes CircuitBreaker to transit: closed => open"); - invocation.metrics.incrementCircuitbreakerOpenedTotal(); - state.open(); - invocation.service.scheduleDelayed(circuitBreaker.delay, state::halfOpen); + openCircuit(invocation, state); } if (failedOn != null) { throw failedOn; @@ -475,6 +471,16 @@ private Object processCircuitBreakerStage(FaultToleranceInvocation invocation, C } } + private void openCircuit(FaultToleranceInvocation invocation, CircuitBreakerState state) throws Exception { + invocation.metrics.incrementCircuitbreakerOpenedTotal(); + state.open(); + if (circuitBreaker.delay == 0L) { + state.halfOpen(); + } else { + invocation.service.runDelayed(circuitBreaker.delay, state::halfOpen); + } + } + /** * Stage that takes care of the {@link TimeoutPolicy} handling. */ @@ -487,7 +493,7 @@ private Object processTimeoutStage(FaultToleranceInvocation invocation, Completa long timeoutTime = System.currentTimeMillis() + timeoutDuration; Thread current = Thread.currentThread(); AtomicBoolean timedOut = new AtomicBoolean(false); - Future timeout = invocation.service.scheduleDelayed(timeoutDuration, () -> { + Future timeout = invocation.service.runDelayed(timeoutDuration, () -> { logger.log(Level.FINE, "Interrupting attempt due to timeout."); timedOut.set(true); current.interrupt(); diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/service/FaultToleranceServiceImpl.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/service/FaultToleranceServiceImpl.java index 5c8a202b6e8..55822a5c7b3 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/service/FaultToleranceServiceImpl.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/service/FaultToleranceServiceImpl.java @@ -340,7 +340,7 @@ public void runAsynchronous(CompletableFuture asyncResult, InvocationCon } @Override - public Future scheduleDelayed(long delayMillis, Runnable task) throws Exception { + public Future runDelayed(long delayMillis, Runnable task) throws Exception { return getManagedScheduledExecutorService().schedule(task, delayMillis, TimeUnit.MILLISECONDS); } diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/policy/BulkheadBasicTest.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/policy/BulkheadBasicTest.java index 6175abd8210..096438239fc 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/policy/BulkheadBasicTest.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/policy/BulkheadBasicTest.java @@ -55,7 +55,6 @@ import org.eclipse.microprofile.faulttolerance.Asynchronous; import org.eclipse.microprofile.faulttolerance.Bulkhead; import org.eclipse.microprofile.faulttolerance.exceptions.BulkheadException; -import org.junit.Before; import org.junit.Test; import fish.payara.microprofile.faulttolerance.FaultToleranceService; @@ -67,13 +66,14 @@ * Tests the basic correctness of {@link Bulkhead} handling. * * @author Jan Bernitt - * */ public class BulkheadBasicTest { final AtomicReference concurrentExecutions = new AtomicReference<>(); final AtomicReference waitingQueuePopulation = new AtomicReference<>(); - final AtomicInteger bulkheadCallCount = new AtomicInteger(); + final AtomicInteger bulkheadWithoutQueueCallCount = new AtomicInteger(); + final AtomicInteger bulkheadWithQueueCallCount = new AtomicInteger(); + final AtomicInteger bulkheadWithQueueInterruptedCallCount = new AtomicInteger(); private final FaultToleranceService service = new FaultToleranceServiceStub() { @Override @@ -89,11 +89,6 @@ public BulkheadSemaphore getWaitingQueuePopulation(int queueCapacity, Invocation } }; - @Before - public void resetCounters() { - bulkheadCallCount.set(0); - } - /** * Makes 2 concurrent request that should succeed acquiring a bulkhead permit. * The 3 attempt fails as no queue is in place without {@link Asynchronous}. @@ -111,18 +106,13 @@ public void bulkheadWithoutQueue() throws Exception { assertProceedingThrowsBulkheadException(annotatedMethod); waiter.complete(null); waitUntilPermitsAquired(0, 0); - assertEquals(2, bulkheadCallCount.get()); + assertEquals(2, bulkheadWithoutQueueCallCount.get()); } @Bulkhead(value = 2) - public String bulkheadWithoutQueue_Method(Future waiter) { - bulkheadCallCount.incrementAndGet(); - try { - waiter.get(); - } catch (Exception e) { - throw new AssertionError(e); - } - return "Success"; + public CompletionStage bulkheadWithoutQueue_Method(Future waiter) { + bulkheadWithoutQueueCallCount.incrementAndGet(); + return waitThenReturnSuccess(waiter); } /** @@ -145,19 +135,14 @@ public void bulkheadWithQueue() throws Exception { assertProceedingThrowsBulkheadException(annotatedMethod); waiter.complete(null); waitUntilPermitsAquired(0, 0); - assertEquals(4, bulkheadCallCount.get()); + assertEquals(4, bulkheadWithQueueCallCount.get()); } @Asynchronous @Bulkhead(value = 2, waitingTaskQueue = 2) public CompletionStage bulkheadWithQueue_Method(Future waiter) { - bulkheadCallCount.incrementAndGet(); - try { - waiter.get(); - } catch (Exception e) { - throw new AssertionError(e); - } - return CompletableFuture.completedFuture("Success"); + bulkheadWithQueueCallCount.incrementAndGet(); + return waitThenReturnSuccess(waiter); } /** @@ -171,6 +156,8 @@ public void bulkheadWithQueueInterrupted() throws Exception { Runnable task = () -> proceedToResultValueOrFail(this, annotatedMethod, waiter); new Thread(task).start(); new Thread(task).start(); + // must wait here to ensure these two threads actually are the ones getting permits + waitUntilPermitsAquired(2, 0); Thread queueing1 = new Thread(task); queueing1.start(); Thread queueing2 = new Thread(task); @@ -182,13 +169,14 @@ public void bulkheadWithQueueInterrupted() throws Exception { waitUntilPermitsAquired(2, 0); waiter.complete(null); waitUntilPermitsAquired(0, 0); - assertEquals(2, bulkheadCallCount.get()); + assertEquals(2, bulkheadWithQueueInterruptedCallCount.get()); } @Asynchronous @Bulkhead(value = 2, waitingTaskQueue = 2) public CompletionStage bulkheadWithQueueInterrupted_Method(Future waiter) { - return bulkheadWithQueue_Method(waiter); + bulkheadWithQueueInterruptedCallCount.incrementAndGet(); + return waitThenReturnSuccess(waiter); } /* @@ -208,6 +196,15 @@ private Object proceedToResultValue(Object test, Method annotatedMethod, Future< return policy.proceed(new StaticAnalysisContext(test, annotatedMethod, argument), service); } + private static CompletionStage waitThenReturnSuccess(Future waiter) throws AssertionError { + try { + waiter.get(); + } catch (Exception e) { + throw new AssertionError(e); + } + return CompletableFuture.completedFuture("Success"); + } + private void assertProceedingThrowsBulkheadException(Method annotatedMethod) throws Exception { try { Object resultValue = proceedToResultValue(this, annotatedMethod, null); @@ -235,7 +232,7 @@ private void waitUntilPermitsAquired(int concurrentExecutions, int waitingQueueP } } - private static boolean equalAcquiredPermits(int expected, BulkheadSemaphore semaphore) { - return semaphore == null ? expected == 0 : semaphore.acquiredPermits() == expected; + private static boolean equalAcquiredPermits(int expected, BulkheadSemaphore actual) { + return actual == null ? expected == 0 : actual.acquiredPermits() == expected; } } diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/policy/CircuitBreakerBasicTest.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/policy/CircuitBreakerBasicTest.java new file mode 100644 index 00000000000..35f47fdfa3b --- /dev/null +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/policy/CircuitBreakerBasicTest.java @@ -0,0 +1,213 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright (c) 2019 Payara Foundation and/or its affiliates. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common Development + * and Distribution License("CDDL") (collectively, the "License"). You + * may not use this file except in compliance with the License. You can + * obtain a copy of the License at + * https://github.com/payara/Payara/blob/master/LICENSE.txt + * See the License for the specific + * language governing permissions and limitations under the License. + * + * When distributing the software, include this License Header Notice in each + * file and include the License file at glassfish/legal/LICENSE.txt. + * + * GPL Classpath Exception: + * The Payara Foundation designates this particular file as subject to the "Classpath" + * exception as provided by the Payara Foundation in the GPL Version 2 section of the License + * file that accompanied this code. + * + * Modifications: + * If applicable, add the following below the License Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyright [year] [name of copyright owner]" + * + * Contributor(s): + * If you wish your version of this file to be governed by only the CDDL or + * only the GPL Version 2, indicate your decision by adding "[Contributor] + * elects to include this software in this distribution under the [CDDL or GPL + * Version 2] license." If you don't indicate a single choice of license, a + * recipient has the option to distribute your version of this file under + * either the CDDL, the GPL Version 2 or to extend the choice of license to + * its licensees as provided above. However, if you add GPL Version 2 code + * and therefore, elected the GPL Version 2 license, then the option applies + * only if the new code is made subject to such option by the copyright + * holder. + */ +package fish.payara.microprofile.faulttolerance.policy; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +import java.lang.reflect.Method; +import java.util.NoSuchElementException; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Future; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; + +import javax.interceptor.InvocationContext; + +import org.eclipse.microprofile.faulttolerance.CircuitBreaker; +import org.eclipse.microprofile.faulttolerance.exceptions.CircuitBreakerOpenException; +import org.junit.Before; +import org.junit.Test; + +import fish.payara.microprofile.faulttolerance.FaultToleranceService; +import fish.payara.microprofile.faulttolerance.service.FaultToleranceServiceStub; +import fish.payara.microprofile.faulttolerance.state.CircuitBreakerState; +import fish.payara.microprofile.faulttolerance.state.CircuitBreakerState.CircuitState; +import fish.payara.microprofile.faulttolerance.test.TestUtils; + +/** + * Tests the basic correctness of {@link CircuitBreaker} handling. + * + * @author Jan Bernitt + */ +public class CircuitBreakerBasicTest { + + private final AtomicInteger circuitBreakerCallCounter = new AtomicInteger(); + final AtomicReference state = new AtomicReference<>(); + final CompletableFuture waitBeforeHalfOpenAgain = new CompletableFuture<>(); + private final FaultToleranceService service = new FaultToleranceServiceStub() { + + @Override + public CircuitBreakerState getState(int requestVolumeThreshold, InvocationContext context) { + return state.updateAndGet(value -> value != null ? value : new CircuitBreakerState(requestVolumeThreshold)); + } + + @Override + public Future runDelayed(long delayMillis, Runnable task) throws Exception { + // test method completes the future when it is ready for execution to proceed and open the circuit (task) + if (waitBeforeHalfOpenAgain.isDone()) { + task.run(); + return CompletableFuture.completedFuture(null); + } + return CompletableFuture.runAsync(() -> { + try { + waitBeforeHalfOpenAgain.get(); + } catch (Exception e) { + // continue + } + task.run(); + }); + } + }; + + @Before + public void resetCounter() { + circuitBreakerCallCounter.set(0); + } + + /** + * The simplest possible {@link CircuitBreaker} without a delay between OPEN and HALF-OPEN whereby OPEN is not + * observable short. + */ + @Test + public void circuitBreakerNoDelay() throws Exception { + assertEquals(1, proceedToResultValue().intValue()); + assertProceedToResultValueFails(IllegalStateException.class); + assertEquals(3, proceedToResultValue().intValue()); + assertProceedToResultValueFails(IllegalStateException.class); + assertEquals(CircuitState.HALF_OPEN, state.get().getCircuitState()); + assertEquals(5, proceedToResultValue().intValue()); + assertProceedToResultValueFails(IllegalStateException.class); + // and so forth, the circuit essentially isn't observable as open since delay is zero + assertEquals(7, proceedToResultValue().intValue()); // success will close again + assertEquals(CircuitState.CLOSED, state.get().getCircuitState()); + } + + @CircuitBreaker(requestVolumeThreshold = 3, delay = 0) + public int circuitBreakerNoDelay_Method() { + return incrementAndFailingOnEveryOtherInvocationOnFirst6(); + } + + /** + * Tests the {@link CircuitBreaker} with delay whereby OPEN state is kept for a time here mocked by waiting for the + * {@link #waitBeforeHalfOpenAgain} future to have the test control timing. + */ + @Test + public void circuitBreakerWithDelay() throws Exception { + assertEquals(1, proceedToResultValue().intValue()); + assertProceedToResultValueFails(IllegalStateException.class); + assertEquals(3, proceedToResultValue().intValue()); + assertProceedToResultValueFails(IllegalStateException.class); + assertEquals(CircuitState.OPEN, state.get().getCircuitState()); + assertProceedToResultValueFails(CircuitBreakerOpenException.class); + assertProceedToResultValueFails(CircuitBreakerOpenException.class); + assertProceedToResultValueFails(CircuitBreakerOpenException.class); + // and it would go on until delay passed (here waiting for waitBeforeHalfOpenAgain): + waitBeforeHalfOpenAgain.complete(null); // now transitions to half-open (async) + waitForState(CircuitState.HALF_OPEN); + assertEquals(CircuitState.HALF_OPEN, state.get().getCircuitState()); + assertEquals(4, circuitBreakerCallCounter.get()); + assertEquals(5, proceedToResultValue().intValue()); + assertProceedToResultValueFails(IllegalStateException.class); + waitForState(CircuitState.HALF_OPEN); + assertEquals(7, proceedToResultValue().intValue()); + assertEquals(8, proceedToResultValue().intValue()); // now we got 2 in a row: closing + assertEquals(CircuitState.CLOSED, state.get().getCircuitState()); + } + + @CircuitBreaker(requestVolumeThreshold = 3, successThreshold = 2) + public int circuitBreakerWithDelay_Method() { + return incrementAndFailingOnEveryOtherInvocationOnFirst6(); + } + + @Test + public void circuitBreakerNotFailingOn() throws Exception { + assertEquals(1, proceedToResultValue().intValue()); + assertProceedToResultValueFails(IllegalStateException.class); + assertEquals(3, proceedToResultValue().intValue()); + assertProceedToResultValueFails(IllegalStateException.class); + assertEquals("Circuit should still be closed as the exception thrown in not matching one the circuit should fail on", + CircuitState.CLOSED, state.get().getCircuitState()); + } + + @CircuitBreaker(requestVolumeThreshold = 3, delay = 0, failOn = NoSuchElementException.class) + public int circuitBreakerNotFailingOn_Method() { + return incrementAndFailingOnEveryOtherInvocationOnFirst6(); + } + + /* + * Helpers + */ + + private void assertProceedToResultValueFails(Class expected) throws Exception { + try { + Integer resultValue = proceedToResultValue(); + fail("Expected the call to fail but it did return: " + resultValue); + } catch (Exception ex) { + assertEquals(expected, ex.getClass()); + } + } + + private Integer proceedToResultValue(Object... methodArguments) throws Exception { + Method annotatedMethod = TestUtils.getAnnotatedMethod(); + FaultTolerancePolicy policy = FaultTolerancePolicy.asAnnotated(getClass(), annotatedMethod); + return (Integer) policy.proceed(new StaticAnalysisContext(this, annotatedMethod, methodArguments), service); + } + + private int incrementAndFailingOnEveryOtherInvocationOnFirst6() { + int resultValue = circuitBreakerCallCounter.incrementAndGet(); + if (resultValue < 7 && resultValue % 2 == 0) { + throw new IllegalStateException("Fails every 2nd invocation"); + } + return resultValue; + } + + private void waitForState(CircuitState expected) { + long delay = 4L; + while (state.get().getCircuitState() != expected) { + try { + Thread.sleep(delay); + } catch (InterruptedException e) { + return; // give up + } + delay *= 2; + } + } +} diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/service/FaultToleranceServiceStub.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/service/FaultToleranceServiceStub.java index 4faef28e54b..6e8643ed6d6 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/service/FaultToleranceServiceStub.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/service/FaultToleranceServiceStub.java @@ -103,7 +103,7 @@ public void delay(long delayMillis, InvocationContext context) throws Interrupte } @Override - public Future scheduleDelayed(long delayMillis, Runnable task) throws Exception { + public Future runDelayed(long delayMillis, Runnable task) throws Exception { throw new UnsupportedOperationException("Override for test case"); } From 2c6a16b8dd6f6e760c16d93291f2be6f50e1df3c Mon Sep 17 00:00:00 2001 From: Jan Bernitt Date: Mon, 29 Apr 2019 09:49:41 +0200 Subject: [PATCH 26/30] PAYARA-3468 corrected typo in method name --- .../faulttolerance/cdi/FaultToleranceExtension.java | 4 ++-- .../faulttolerance/service/FaultToleranceUtils.java | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/cdi/FaultToleranceExtension.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/cdi/FaultToleranceExtension.java index aae1f883b4a..8360a75d307 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/cdi/FaultToleranceExtension.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/cdi/FaultToleranceExtension.java @@ -93,11 +93,11 @@ void beforeBeanDiscovery(@Observes BeforeBeanDiscovery beforeBeanDiscovery, Bean void processAnnotatedType(@Observes @WithAnnotations({ Asynchronous.class, Bulkhead.class, CircuitBreaker.class, Fallback.class, Retry.class, Timeout.class }) ProcessAnnotatedType processAnnotatedType) throws Exception { boolean markAllMethods = FaultToleranceUtils - .isAnnotaetdWithFaultToleranceAnnotations(processAnnotatedType.getAnnotatedType()); + .isAnnotatedWithFaultToleranceAnnotations(processAnnotatedType.getAnnotatedType()); Class targetClass = processAnnotatedType.getAnnotatedType().getJavaClass(); for (AnnotatedMethodConfigurator methodConfigurator : processAnnotatedType.configureAnnotatedType().methods()) { if (markAllMethods || FaultToleranceUtils - .isAnnotaetdWithFaultToleranceAnnotations(methodConfigurator.getAnnotated())) { + .isAnnotatedWithFaultToleranceAnnotations(methodConfigurator.getAnnotated())) { FaultTolerancePolicy.asAnnotated(targetClass, methodConfigurator.getAnnotated().getJavaMember()); methodConfigurator.add(MARKER); } diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/service/FaultToleranceUtils.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/service/FaultToleranceUtils.java index ccc9f6819cd..2401e7b9bfc 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/service/FaultToleranceUtils.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/service/FaultToleranceUtils.java @@ -209,7 +209,7 @@ public static String getCanonicalMethodName(InvocationContext context) { return getPlainCanonicalName(context.getMethod().getDeclaringClass()) + "." + context.getMethod().getName(); } - public static boolean isAnnotaetdWithFaultToleranceAnnotations(Annotated element) { + public static boolean isAnnotatedWithFaultToleranceAnnotations(Annotated element) { return element.isAnnotationPresent(Asynchronous.class) || element.isAnnotationPresent(Bulkhead.class) || element.isAnnotationPresent(CircuitBreaker.class) From 02438167d3edc2ba50cf8bc09a573928f7d424a8 Mon Sep 17 00:00:00 2001 From: Jan Bernitt Date: Mon, 29 Apr 2019 17:15:27 +0200 Subject: [PATCH 27/30] PAYARA-3468 added stress test with multiple concurrent callers --- .../microprofile/fault-tolerance/pom.xml | 6 + .../policy/BulkheadBasicTest.java | 4 +- .../policy/FaultToleranceStressTest.java | 328 ++++++++++++++++++ 3 files changed, 336 insertions(+), 2 deletions(-) create mode 100644 appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/policy/FaultToleranceStressTest.java diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/pom.xml b/appserver/payara-appserver-modules/microprofile/fault-tolerance/pom.xml index 2f806d91610..b775a4ef7ea 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/pom.xml +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/pom.xml @@ -93,5 +93,11 @@ javaee-api ${javaee.api.version} + + org.hamcrest + hamcrest + 2.1 + test + diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/policy/BulkheadBasicTest.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/policy/BulkheadBasicTest.java index 096438239fc..a8c0b56d9e0 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/policy/BulkheadBasicTest.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/policy/BulkheadBasicTest.java @@ -79,13 +79,13 @@ public class BulkheadBasicTest { @Override public BulkheadSemaphore getConcurrentExecutions(int maxConcurrentThreads, InvocationContext context) { return concurrentExecutions.updateAndGet(value -> - value != null ? value : new BulkheadSemaphore(maxConcurrentThreads)); + value != null ? value : new BulkheadSemaphore(maxConcurrentThreads)); } @Override public BulkheadSemaphore getWaitingQueuePopulation(int queueCapacity, InvocationContext context) { return waitingQueuePopulation.updateAndGet(value -> - value != null ? value : new BulkheadSemaphore(queueCapacity)); + value != null ? value : new BulkheadSemaphore(queueCapacity)); } }; diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/policy/FaultToleranceStressTest.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/policy/FaultToleranceStressTest.java new file mode 100644 index 00000000000..9888e68540b --- /dev/null +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/test/java/fish/payara/microprofile/faulttolerance/policy/FaultToleranceStressTest.java @@ -0,0 +1,328 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright (c) 2019 Payara Foundation and/or its affiliates. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common Development + * and Distribution License("CDDL") (collectively, the "License"). You + * may not use this file except in compliance with the License. You can + * obtain a copy of the License at + * https://github.com/payara/Payara/blob/master/LICENSE.txt + * See the License for the specific + * language governing permissions and limitations under the License. + * + * When distributing the software, include this License Header Notice in each + * file and include the License file at glassfish/legal/LICENSE.txt. + * + * GPL Classpath Exception: + * The Payara Foundation designates this particular file as subject to the "Classpath" + * exception as provided by the Payara Foundation in the GPL Version 2 section of the License + * file that accompanied this code. + * + * Modifications: + * If applicable, add the following below the License Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyright [year] [name of copyright owner]" + * + * Contributor(s): + * If you wish your version of this file to be governed by only the CDDL or + * only the GPL Version 2, indicate your decision by adding "[Contributor] + * elects to include this software in this distribution under the [CDDL or GPL + * Version 2] license." If you don't indicate a single choice of license, a + * recipient has the option to distribute your version of this file under + * either the CDDL, the GPL Version 2 or to extend the choice of license to + * its licensees as provided above. However, if you add GPL Version 2 code + * and therefore, elected the GPL Version 2 license, then the option applies + * only if the new code is made subject to such option by the copyright + * holder. + */ +package fish.payara.microprofile.faulttolerance.policy; + +import static org.hamcrest.Matchers.greaterThan; +import static org.hamcrest.Matchers.greaterThanOrEqualTo; +import static org.hamcrest.Matchers.lessThan; +import static org.hamcrest.Matchers.lessThanOrEqualTo; +import static org.hamcrest.Matchers.oneOf; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicReference; + +import javax.interceptor.InvocationContext; + +import org.eclipse.microprofile.faulttolerance.Asynchronous; +import org.eclipse.microprofile.faulttolerance.Bulkhead; +import org.eclipse.microprofile.faulttolerance.CircuitBreaker; +import org.eclipse.microprofile.faulttolerance.ExecutionContext; +import org.eclipse.microprofile.faulttolerance.Fallback; +import org.eclipse.microprofile.faulttolerance.FallbackHandler; +import org.eclipse.microprofile.faulttolerance.Retry; +import org.eclipse.microprofile.faulttolerance.exceptions.FaultToleranceException; +import org.junit.Test; + +import fish.payara.microprofile.faulttolerance.FaultToleranceService; +import fish.payara.microprofile.faulttolerance.service.FaultToleranceServiceStub; +import fish.payara.microprofile.faulttolerance.state.BulkheadSemaphore; +import fish.payara.microprofile.faulttolerance.state.CircuitBreakerState; +import fish.payara.microprofile.faulttolerance.state.CircuitBreakerState.CircuitState; +import fish.payara.microprofile.faulttolerance.test.TestUtils; + +/** + * Tests that uses multiple concurrent callers for the method under test to see that "statistically" it behaves correctly. + * + * @author Jan Bernitt + */ +public class FaultToleranceStressTest implements FallbackHandler> { + + private static final int NUMBER_OF_CALLERS = 8; + private static final int NUMBER_OF_CALLS = 10; + + private final AtomicInteger methodInvocationCount = new AtomicInteger(); + private final AtomicInteger methodFailedInvocationCount = new AtomicInteger(); + private final AtomicInteger methodSuccessfulInvocationCount = new AtomicInteger(); + + private final AtomicInteger callerFailedInvocationCount = new AtomicInteger(); + private final AtomicInteger callerExpectedlyFailedInvocationCount = new AtomicInteger(); + private final AtomicInteger callerSuccessfulInvocationCount = new AtomicInteger(); + private final AtomicInteger callerUnexpectedOutcomeInvocationCount = new AtomicInteger(); + + final AtomicInteger delayedExecutionCount = new AtomicInteger(); + final AtomicLong delayedMillis = new AtomicLong(); + final AtomicLong maxDelayMillis = new AtomicLong(Long.MIN_VALUE); + final AtomicLong minDelayMillis = new AtomicLong(Long.MAX_VALUE); + + final AtomicInteger asyncCompletedCount = new AtomicInteger(); + final AtomicInteger asyncCompletedExceptionallyCount = new AtomicInteger(); + final AtomicInteger asyncCancelCount = new AtomicInteger(); + + final AtomicInteger circuitStateAccessCount = new AtomicInteger(); + final AtomicReference state = new AtomicReference<>(); + + final AtomicInteger concurrentExecutionsAccessCount = new AtomicInteger(); + final AtomicReference concurrentExecutions = new AtomicReference<>(); + + final AtomicInteger waitingQueuePopulationAccessCount = new AtomicInteger(); + final AtomicReference waitingQueuePopulation = new AtomicReference<>(); + + final ExecutorService executorService = Executors.newWorkStealingPool(NUMBER_OF_CALLERS / 2); + final FaultToleranceService service = new FaultToleranceServiceStub() { + + @Override + public CircuitBreakerState getState(int requestVolumeThreshold, InvocationContext context) { + circuitStateAccessCount.incrementAndGet(); + return state.updateAndGet(value -> value != null ? value : new CircuitBreakerState(requestVolumeThreshold)); + } + + @Override + public BulkheadSemaphore getConcurrentExecutions(int maxConcurrentThreads, InvocationContext context) { + concurrentExecutionsAccessCount.incrementAndGet(); + return concurrentExecutions.updateAndGet(value -> + value != null ? value : new BulkheadSemaphore(maxConcurrentThreads)); + } + + @Override + public BulkheadSemaphore getWaitingQueuePopulation(int queueCapacity, InvocationContext context) { + waitingQueuePopulationAccessCount.incrementAndGet(); + return waitingQueuePopulation.updateAndGet(value -> + value != null ? value : new BulkheadSemaphore(queueCapacity)); + } + + @Override + public void delay(long delayMillis, InvocationContext context) throws InterruptedException { + // we don't really wait in this test but we count waiting time + delayedExecutionCount.incrementAndGet(); + maxDelayMillis.updateAndGet(value -> Math.max(value, delayMillis)); + minDelayMillis.updateAndGet(value -> Math.min(value, delayMillis)); + delayedMillis.addAndGet(delayMillis); + } + + @Override + public void runAsynchronous(CompletableFuture asyncResult, InvocationContext context, + Callable task) throws RejectedExecutionException { + Runnable completionTask = () -> { + if (!asyncResult.isCancelled() && !Thread.currentThread().isInterrupted()) { + try { + Future futureResult = AsynchronousPolicy.toFuture(task.call()); + if (!asyncResult.isCancelled()) { + if (!asyncResult.isDone()) { + asyncCompletedCount.incrementAndGet(); + asyncResult.complete(futureResult.get()); + } + } else { + asyncCancelCount.incrementAndGet(); + futureResult.cancel(true); + } + } catch (Exception ex) { + asyncCompletedExceptionallyCount.incrementAndGet(); + asyncResult.completeExceptionally(ex); + } + } + }; + executorService.execute(completionTask); + } + }; + + @Test + public void occasionallyFailingService() throws InterruptedException { + Method annotatedMethod = TestUtils.getAnnotatedMethod(); + Runnable callerTask = () -> callServiceMethod(annotatedMethod); + List callers = startCallers(NUMBER_OF_CALLERS, callerTask); + waitForCallersToFinish(callers); + assertTrue("All callers should be done and removed by now", callers.isEmpty()); + + // check the counts make sense + assertEquals("No unexpected outcome should occur", 0, callerUnexpectedOutcomeInvocationCount.get()); + int totalSuccesses = callerSuccessfulInvocationCount.get(); + int totalFailures = callerFailedInvocationCount.get(); + int totalExpectedCalls = NUMBER_OF_CALLERS * NUMBER_OF_CALLS; + int minimumExpectedRetries = totalExpectedCalls / 3; + assertEquals(totalExpectedCalls, totalSuccesses + totalFailures); + assertThat("Retry should lead one successful attempt and at least one failure attempt per failure outcome", + methodInvocationCount.get(), greaterThanOrEqualTo(totalSuccesses + minimumExpectedRetries)); + assertThat("Every 5th attempt should return with a Future complected exceptionally", + callerExpectedlyFailedInvocationCount.get(), greaterThan(0)); // open circuit makes this mostly unpredictable + assertThat("Some atempt should end up waiting", + waitingQueuePopulationAccessCount.get(), greaterThan(0)); + assertThat("Most attempts should go through bulkhead execution", // due to open circuit some might never reach bulkhead + concurrentExecutionsAccessCount.get(), greaterThanOrEqualTo(totalExpectedCalls / 2)); // conservative assumption: half of normal case number + assertThat("Each attempt should use cuircuit breaker state", + circuitStateAccessCount.get(), greaterThanOrEqualTo(totalExpectedCalls)); + assertThat("Each successful attempt should have been asyncronous", + asyncCompletedCount.get(), greaterThan(totalExpectedCalls)); + assertThat("Each failing attempt should have been asyncronous", + asyncCompletedExceptionallyCount.get(), greaterThanOrEqualTo(totalFailures)); + assertEquals("Cancel should not have occured", 0, asyncCancelCount.get()); + assertThat("Most attempt throwing an exception should cause a delay", // not all since retry can be cancelled due to concurrent completion + delayedExecutionCount.get(), greaterThanOrEqualTo(minimumExpectedRetries / 2)); // conservative assumption: half of normal case number + long totalDelayMillis = delayedMillis.get(); + assertThat("Some attempts should have tried to retry with a delay", + totalDelayMillis, greaterThan(0L)); + assertThat("Total delay should be less than the sum of maximal jitter per retry", + totalDelayMillis, lessThan(methodInvocationCount.get() * 200L)); // 200ms being the jitter + assertThat("All attempts should use a non negative delay", + maxDelayMillis.get(), greaterThanOrEqualTo(0L)); + assertThat("All attempts should use a delay not larger than the jitter", + maxDelayMillis.get(), lessThanOrEqualTo(200L)); + + // now check that the state makes sense + assertEquals("No execution should ongo", 0, concurrentExecutions.get().acquiredPermits()); + assertEquals("No queueing should ongo", 0, waitingQueuePopulation.get().acquiredPermits()); + assertThat("Circuit should not be open (any more)", + state.get().getCircuitState(), oneOf(CircuitState.HALF_OPEN, CircuitState.CLOSED)); + } + + /** + * The method under tests fails every 3rd call whereby in a window of 4 there can be 2 failed calls opening the + * circuit. As delay is just recorded but not enforced there is only a minimal chance another caller does an attempt + * while the circuit is open but occasionally this happens. + * + * Every 5th call returns a failed Future that should be handed to the caller as is. + * + * Every 3rd call fails causing a retry. Together with open circuits this might even cause fallback handler to be + * used which will also fail the result as it only rethrows the error. + */ + @Asynchronous + @Fallback(FaultToleranceStressTest.class) + @Retry(maxRetries = 1) + @Bulkhead(value = 2, waitingTaskQueue = 2) + @CircuitBreaker(successThreshold = 2, delay = 0, requestVolumeThreshold = 4) + public Future occasionallyFailingService_Method() throws IOException { + int called = methodInvocationCount.incrementAndGet(); + if (called % 3 == 0) { + // this causes a retry + methodFailedInvocationCount.incrementAndGet(); + throw new IOException("Failed"); + } + if (called % 5 == 0) { + // this does not cause a retry, its simply a Future that completes with a failure + CompletableFuture failedValue = new CompletableFuture<>(); + failedValue.completeExceptionally(new IOException("Failed")); + return failedValue; + } + methodSuccessfulInvocationCount.incrementAndGet(); + return CompletableFuture.completedFuture("Success"); + } + + @Override + public Future handle(ExecutionContext context) { + if (context.getFailure() instanceof FaultToleranceException) { + throw (FaultToleranceException) context.getFailure(); + } + throw new UncheckedIOException((IOException) context.getFailure()); + } + + private void callServiceMethod(Method annotatedMethod) { + Object test = this; + FaultTolerancePolicy policy = FaultTolerancePolicy.asAnnotated(test.getClass(), annotatedMethod); + for (int i = 0; i < NUMBER_OF_CALLS; i++) { + try { + @SuppressWarnings("unchecked") + Future resultValue = (Future) + policy.proceed(new StaticAnalysisContext(test, annotatedMethod), service); + assertEquals("Success", resultValue.get()); + callerSuccessfulInvocationCount.incrementAndGet(); + } catch (ExecutionException ex) { + callerFailedInvocationCount.incrementAndGet(); + if (ex.getCause() instanceof UncheckedIOException) { + // last retry after exception(s) threw exception which comes back from fallback handler as UncheckedIOException + assertEquals("Failed", ex.getCause().getCause().getMessage()); + } else if (ex.getCause() instanceof IOException) { + // failed since Future completed with an exception + assertEquals("Failed", ex.getCause().getMessage()); + callerExpectedlyFailedInvocationCount.incrementAndGet(); + } else if (ex.getCause() instanceof FaultToleranceException) { + // circuit open or bulkhead queue full + } else { + callerUnexpectedOutcomeInvocationCount.incrementAndGet(); + } + } catch (Exception ex) { + callerUnexpectedOutcomeInvocationCount.incrementAndGet(); + } catch (AssertionError err) { + callerUnexpectedOutcomeInvocationCount.incrementAndGet(); + } + } + } + + private static List startCallers(int count, Runnable callerTask) { + List callers = new ArrayList<>(); + for (int i = 0; i < count; i++) { + Thread caller = new Thread(callerTask); + callers.add(caller); + caller.start(); + } + return callers; + } + + private static void waitForCallersToFinish(List callers) throws InterruptedException { + Iterator iter = callers.iterator(); + while (iter.hasNext()) { + Thread caller = iter.next(); + iter.remove(); + try { + caller.join(); + } catch (InterruptedException ex) { + for (Thread c : callers) { + c.interrupt(); + } + throw ex; + } + } + } +} From 34b11d32db05f3ea2fbd0a9d2bb21b4eb28a965c Mon Sep 17 00:00:00 2001 From: Andrew Pielage Date: Tue, 30 Apr 2019 16:13:13 +0100 Subject: [PATCH 28/30] Indenting --- .../faulttolerance/cdi/FaultToleranceExtension.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/cdi/FaultToleranceExtension.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/cdi/FaultToleranceExtension.java index 8360a75d307..b9ceac59cd7 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/cdi/FaultToleranceExtension.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/cdi/FaultToleranceExtension.java @@ -109,8 +109,8 @@ void changeInterceptorPriority(@Observes ProcessAnnotatedType annotation instanceof Priority) - .add(new PriorityLiteral(priorityOverride.get())); + .remove(annotation -> annotation instanceof Priority) + .add(new PriorityLiteral(priorityOverride.get())); } } From 7b579aa66739c75cc677a72bc5b1f48f2230a5c2 Mon Sep 17 00:00:00 2001 From: Andrew Pielage Date: Tue, 30 Apr 2019 16:53:37 +0100 Subject: [PATCH 29/30] Typo in method names --- .../faulttolerance/policy/MethodLookupUtils.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/MethodLookupUtils.java b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/MethodLookupUtils.java index 88c2b272397..cc61240e072 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/MethodLookupUtils.java +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/src/main/java/fish/payara/microprofile/faulttolerance/policy/MethodLookupUtils.java @@ -81,22 +81,22 @@ public static Method findMethodWithMatchingNameAndArguments(String name, Method } private static boolean isMatchingParameterList(Method sample, Method candidate) { - return isMatchtingParameterList(sample.getGenericParameterTypes(), candidate.getGenericParameterTypes()); + return isMatchingParameterList(sample.getGenericParameterTypes(), candidate.getGenericParameterTypes()); } - private static boolean isMatchtingParameterList(Type[] samples, Type[] candidates) { + private static boolean isMatchingParameterList(Type[] samples, Type[] candidates) { if (samples.length != candidates.length) { return false; } for (int i = 0; i < samples.length; i++) { - if (!isMatchtingType(samples[i], candidates[i])) { + if (!isMatchingType(samples[i], candidates[i])) { return false; } } return true; } - private static boolean isMatchtingType(Type sample, Type candidate) { + private static boolean isMatchingType(Type sample, Type candidate) { return sample.equals(candidate) || (candidate instanceof TypeVariable) || (candidate instanceof GenericArrayType) @@ -108,8 +108,8 @@ private static boolean isMatchingWildcardType(Type sample, Type candidate) { if (sample instanceof WildcardType && candidate instanceof WildcardType) { WildcardType sampleType = (WildcardType) sample; WildcardType candidateType = (WildcardType) candidate; - return isMatchtingParameterList(sampleType.getLowerBounds(), candidateType.getLowerBounds()) - && isMatchtingParameterList(sampleType.getUpperBounds(), candidateType.getUpperBounds()); + return isMatchingParameterList(sampleType.getLowerBounds(), candidateType.getLowerBounds()) + && isMatchingParameterList(sampleType.getUpperBounds(), candidateType.getUpperBounds()); } return false; } @@ -121,7 +121,7 @@ private static boolean isMatchingGenericType(Type sample, Type candidate) { if (sampleType.getRawType() != candidateType.getRawType()) { return false; } - return isMatchtingParameterList(sampleType.getActualTypeArguments(), + return isMatchingParameterList(sampleType.getActualTypeArguments(), candidateType.getActualTypeArguments()); } return false; From 2ce38368ce960cec4977084d0bda820eb752a0e0 Mon Sep 17 00:00:00 2001 From: Jan Bernitt Date: Wed, 1 May 2019 10:50:11 +0200 Subject: [PATCH 30/30] PAYARA-3468 added hamcrest to dependency management --- .../microprofile/fault-tolerance/pom.xml | 2 -- nucleus/pom.xml | 6 ++++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/appserver/payara-appserver-modules/microprofile/fault-tolerance/pom.xml b/appserver/payara-appserver-modules/microprofile/fault-tolerance/pom.xml index b775a4ef7ea..0cf399cd1c0 100644 --- a/appserver/payara-appserver-modules/microprofile/fault-tolerance/pom.xml +++ b/appserver/payara-appserver-modules/microprofile/fault-tolerance/pom.xml @@ -96,8 +96,6 @@ org.hamcrest hamcrest - 2.1 - test diff --git a/nucleus/pom.xml b/nucleus/pom.xml index df1de015d37..3217b1bc42d 100644 --- a/nucleus/pom.xml +++ b/nucleus/pom.xml @@ -1301,6 +1301,12 @@ Parent is ${project.parent} jmockit 1.32 + + org.hamcrest + hamcrest + 2.1 + test + commons-io commons-io