All Downloads are FREE. Search and download functionalities are using the official Maven repository.

fish.payara.microprofile.faulttolerance.FaultToleranceService Maven / Gradle / Ivy

There is a newer version: 7.2024.1.Alpha1
Show newest version
/*
 * 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;

import fish.payara.microprofile.faulttolerance.state.CircuitBreakerState;
import fish.payara.nucleus.requesttracing.RequestTracingService;
import fish.payara.nucleus.requesttracing.domain.RequestEvent;
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.logging.Level;
import java.util.logging.Logger;
import javax.annotation.PostConstruct;
import javax.enterprise.concurrent.ManagedExecutorService;
import javax.enterprise.concurrent.ManagedScheduledExecutorService;
import javax.inject.Inject;
import javax.inject.Named;
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.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.InvocationManager;
import org.glassfish.hk2.api.ServiceLocator;
import org.glassfish.hk2.runlevel.RunLevel;
import org.glassfish.internal.data.ApplicationInfo;
import org.glassfish.internal.deployment.Deployment;
import org.jvnet.hk2.annotations.Optional;
import org.jvnet.hk2.annotations.Service;

/**
 * Base Service for MicroProfile Fault Tolerance.
 * 
 * @author Andrew Pielage
 */
@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 FALLBACK_HANDLER_METHOD_NAME = "handle";
    
    private static final Logger logger = Logger.getLogger(FaultToleranceService.class.getName());
    
    @Inject
    @Named(ServerEnvironment.DEFAULT_INSTANCE_NAME)
    @Optional
    FaultToleranceServiceConfiguration faultToleranceServiceConfiguration;
    
    @Inject
    RequestTracingService requestTracingService;
    
    @Inject
    ServiceLocator habitat;
    
    @Inject
    Events events;
    
    private final Map enabledMap;
    private final Map> bulkheadExecutionSemaphores;
    private final Map> bulkheadExecutionQueueSemaphores;
    private final Map> circuitBreakerStates;
    
    public FaultToleranceService() {
        enabledMap = new ConcurrentHashMap<>();
        bulkheadExecutionSemaphores  = new ConcurrentHashMap<>();
        bulkheadExecutionQueueSemaphores = new ConcurrentHashMap<>();
        circuitBreakerStates = new ConcurrentHashMap<>();
    }
    
    @PostConstruct
    public void postConstruct() {
        events.register(this);
        faultToleranceServiceConfiguration = habitat.getService(FaultToleranceServiceConfiguration.class);
        requestTracingService = habitat.getService(RequestTracingService.class);
    }
    
    @Override
    public void event(Event event) {
        if (event.is(Deployment.APPLICATION_UNLOADED)) {
            ApplicationInfo info = (ApplicationInfo) event.hook();
            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 (enabledMap.containsKey(applicationName)) {
                return enabledMap.get(applicationName);
            } else {
                setFaultToleranceEnabled(applicationName, config);
                return enabledMap.get(applicationName);
            }
        } catch (NullPointerException npe) {
            setFaultToleranceEnabled(applicationName, config);
            return enabledMap.get(applicationName);
        }
        
    }
    
    /**
     * 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
     */
    private synchronized void setFaultToleranceEnabled(String applicationName, Config config) {
        try {
            // 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 (!enabledMap.containsKey(applicationName)) {
                if (config != null) {
                    // Set the enabled value to the override value from the config, or true if it isn't configured
                    enabledMap.put(applicationName, 
                        config.getOptionalValue(FAULT_TOLERANCE_ENABLED_PROPERTY, Boolean.class).orElse(Boolean.TRUE));
                } else {
                    logger.log(Level.FINE, "No config found, so enabling fault tolerance for application: {0}",
                            applicationName);
                    enabledMap.put(applicationName, Boolean.TRUE);
                }
            }
        } catch (NullPointerException npe) {
            logger.log(Level.FINE, "Caught null pointer attempting to register application: " + applicationName, npe);
            if (config != null) {
                // Set the enabled value to the override value from the config, or true if it isn't configured
                enabledMap.put(applicationName, 
                    config.getOptionalValue(FAULT_TOLERANCE_ENABLED_PROPERTY, Boolean.class).orElse(Boolean.TRUE));
            } else {
                logger.log(Level.FINE, "No config found, so enabling fault tolerance for application: {0}",
                        applicationName);
                enabledMap.put(applicationName, Boolean.TRUE);
            }
        }
    }
    
    /**
     * 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();
        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;
    }
    
    /**
     * 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
                .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(
                    "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");
            } 
        }
        
        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 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 Semaphore getBulkheadExecutionSemaphore(String applicationName, Method annotatedMethod, int bulkheadValue) {
        Semaphore bulkheadExecutionSemaphore;
        String fullMethodSignature = getFullMethodSignature(annotatedMethod);
        
        Map annotatedMethodSemaphores = bulkheadExecutionSemaphores.get(applicationName);
        
        // If there isn't a semaphore registered for this application name, register one, otherwise just return
        // the one already registered
        if (annotatedMethodSemaphores == null) {
            logger.log(Level.FINER, "No matching application in the bulkhead execution semaphore map, registering...");
            bulkheadExecutionSemaphore = createBulkheadExecutionSemaphore(applicationName, 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, fullMethodSignature,
                        bulkheadValue);
            }
        }
        
        return bulkheadExecutionSemaphore;
    }
    
    /**
     * Helper method to create and register a Bulkhead Execution Semaphore for an annotated method
     * @param applicationName The application name to register the semaphore against
     * @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 Semaphore createBulkheadExecutionSemaphore(String applicationName, 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 (bulkheadExecutionSemaphores.get(applicationName) == null) {
            logger.log(Level.FINER, "Registering application to the bulkhead execution semaphore map: {0}", 
                    applicationName);
            bulkheadExecutionSemaphores.put(applicationName, 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 (bulkheadExecutionSemaphores.get(applicationName).get(fullMethodSignature) == null) {
            logger.log(Level.FINER, "Registering semaphore for method {0} to the bulkhead execution semaphore map for "
                    + "application {1}", new String[]{fullMethodSignature, applicationName});
            bulkheadExecutionSemaphores.get(applicationName).put(fullMethodSignature, new Semaphore(bulkheadValue, 
                    true));
        }

        return bulkheadExecutionSemaphores.get(applicationName).get(fullMethodSignature);
    }
    
    /**
     * 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 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 Semaphore getBulkheadExecutionQueueSemaphore(String applicationName, Method annotatedMethod,
            int bulkheadWaitingTaskQueue) {
        Semaphore bulkheadExecutionQueueSemaphore;
        String fullMethodSignature = getFullMethodSignature(annotatedMethod);
        
        Map annotatedMethodExecutionQueueSemaphores = 
                bulkheadExecutionQueueSemaphores.get(applicationName);
        
        // 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, 
                    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, 
                        fullMethodSignature, bulkheadWaitingTaskQueue);
            }
        }
        
        return bulkheadExecutionQueueSemaphore;
    }
    
    /**
     * Helper method to create and register a Bulkhead Execution Queue Semaphore for an annotated method
     * @param applicationName The application name to register the semaphore against
     * @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 Semaphore createBulkheadExecutionQueueSemaphore(String applicationName, 
            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 application to "
                + "the bulkhead execution queue semaphore map");
        if (bulkheadExecutionQueueSemaphores.get(applicationName) == null) {
            logger.log(Level.FINER, "Registering application to the bulkhead execution queue semaphore map: {0}", 
                    applicationName);
            bulkheadExecutionQueueSemaphores.put(applicationName, 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 (bulkheadExecutionQueueSemaphores.get(applicationName).get(fullMethodSignature) == null) {
            logger.log(Level.FINER, "Registering semaphore for method {0} to the bulkhead execution semaphore map for "
                    + "application {1}", new String[]{fullMethodSignature, applicationName});
            bulkheadExecutionQueueSemaphores.get(applicationName)
                    .put(fullMethodSignature, new Semaphore(bulkheadWaitingTaskQueue, true));
        }

        return bulkheadExecutionQueueSemaphores.get(applicationName).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.
     * @param applicationName The application name
     * @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
     */
    public CircuitBreakerState getCircuitBreakerState(String applicationName, Method annotatedMethod, 
            CircuitBreaker circuitBreaker) {
        CircuitBreakerState circuitBreakerState;
        String fullMethodSignature = getFullMethodSignature(annotatedMethod);
        
        Map annotatedMethodCircuitBreakerStates = 
                circuitBreakerStates.get(applicationName);

        // 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 application in the circuit breaker states map, registering...");
            circuitBreakerState = registerCircuitBreaker(applicationName, fullMethodSignature, circuitBreaker);
        } 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, fullMethodSignature, circuitBreaker);
            }
        }
        
        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, String fullMethodSignature, 
            CircuitBreaker circuitBreaker) {
        // 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 circuit breaker states map");
        if (circuitBreakerStates.get(applicationName) == null) {
            logger.log(Level.FINER, "Registering application to the circuit breaker states map: {0}", 
                    applicationName);
            circuitBreakerStates.put(applicationName, 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 (circuitBreakerStates.get(applicationName).get(fullMethodSignature) == null) {
            logger.log(Level.FINER, "Registering CircuitBreakerState for method {0} to the circuit breaker states map "
                    + "for application {1}", new String[]{fullMethodSignature, applicationName});
            circuitBreakerStates.get(applicationName)
                    .put(fullMethodSignature, new CircuitBreakerState(circuitBreaker.requestVolumeThreshold()));
        }

        return circuitBreakerStates.get(applicationName).get(fullMethodSignature);
    }

    /**
     * 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) {
        enabledMap.remove(applicationName);
        deregisterCircuitBreaker(applicationName);
        deregisterBulkhead(applicationName);
    }
    
    /**
     * Removes the given application name from the circuit breaker states map
     * @param applicationName The name of the application to remove
     */
    private void deregisterCircuitBreaker(String applicationName) {
        circuitBreakerStates.remove(applicationName);
    }
    
    /**
     * Removes the given application name from the bulkhead maps
     * @param applicationName The name of the application to remove
     */
    private void deregisterBulkhead(String applicationName) {
        bulkheadExecutionSemaphores.remove(applicationName);
        bulkheadExecutionQueueSemaphores.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
     * @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 (appName == null) {
                    appName = getFullMethodSignature(invocationContext.getMethod());
                }
            }
        }
        
        return appName;
    }
    
    /**
     * 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 String getFullMethodSignature(Method annotatedMethod) {
        return annotatedMethod.getDeclaringClass().getCanonicalName() 
                + "#" + annotatedMethod.getName() 
                + "(" + Arrays.toString(annotatedMethod.getParameterTypes()) + ")"
                + ">" + annotatedMethod.getReturnType().getSimpleName();
    }
    
    public void traceFaultToleranceEvent(RequestEvent requestEvent, InvocationManager invocationManager, 
            InvocationContext invocationContext) {
        if (requestTracingService != null && requestTracingService.isRequestTracingEnabled()) {
            addGenericFaultToleranceRequestTracingDetails(requestEvent, invocationManager, invocationContext);
            requestTracingService.traceRequestEvent(requestEvent);
        }
    }
    
    private void addGenericFaultToleranceRequestTracingDetails(RequestEvent requestEvent, 
            InvocationManager invocationManager, InvocationContext invocationContext) {
        requestEvent.addProperty("App Name", invocationManager.getCurrentInvocation().getAppName());
        requestEvent.addProperty("Component ID", invocationManager.getCurrentInvocation().getComponentId());
        requestEvent.addProperty("Module Name", invocationManager.getCurrentInvocation().getModuleName());
        requestEvent.addProperty("Class Name", invocationContext.getMethod().getDeclaringClass().getName());
        requestEvent.addProperty("Method Name", invocationContext.getMethod().getName());
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy