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

com.tangosol.net.DefaultCacheServer Maven / Gradle / Ivy

There is a newer version: 24.09
Show newest version
/*
 * Copyright (c) 2000, 2024, Oracle and/or its affiliates.
 *
 * Licensed under the Universal Permissive License v 1.0 as shown at
 * https://oss.oracle.com/licenses/upl.
 */
package com.tangosol.net;

import com.oracle.coherence.common.base.Blocking;

import com.tangosol.application.ContainerContext;

import com.tangosol.application.Context;
import com.tangosol.application.LifecycleListener;

import com.tangosol.coherence.config.Config;

import com.tangosol.internal.net.metrics.MetricsHttpHelper;
import com.tangosol.internal.net.service.LegacyXmlServiceHelper;

import com.tangosol.net.security.DoAsAction;

import com.tangosol.run.xml.XmlElement;
import com.tangosol.run.xml.XmlHelper;

import com.tangosol.util.Base;
import com.tangosol.util.ClassHelper;

import java.io.File;

import java.security.AccessController;
import java.security.PrivilegedAction;

import java.util.Enumeration;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.ServiceLoader;

import java.util.concurrent.CopyOnWriteArrayList;

import java.util.regex.Pattern;


/**
* DefaultCacheServer is a simple command line facility and convenience API
* that starts all services that are declared as requiring an "autostart" in
* the configurable factory XML descriptor.
* 

* DefaultCacheServer can also monitor services it started to ensure they * exist. Monitoring services is enabled by default. * * @author gg/yc/hr * @since Coherence 2.2 */ public class DefaultCacheServer extends Base { // ----- constructors --------------------------------------------------- /** * Construct a DefaultCacheServer using the provided factory. * * @param factory the ConfigurableCacheFactory */ public DefaultCacheServer(ConfigurableCacheFactory factory) { m_factory = factory; } // ----- public API ----------------------------------------------------- /** * Add a {@link LifecycleListener} that will be notified of lifecycle * events for this {@link DefaultCacheServer}. * * @param listener the listener to add */ public void addLifecycleListener(LifecycleListener listener) { ensureLifecycleListeners().add(listener); } /** * Remove a {@link LifecycleListener} so that it will no longer be notified * of lifecycle events for this {@link DefaultCacheServer}. *

* Listeners are stored in a {@link List} and will be removed based on a * simple object equality check. * * @param listener the listener to remove */ public void removeLifecycleListener(LifecycleListener listener) { ensureLifecycleListeners().remove(listener); } /** * Start the cache server and check the service status periodically, * restarting any stopped services. *

* This method will block the calling thread. * * @param cWaitMillis the number of milliseconds between checks */ public void startAndMonitor(final long cWaitMillis) { AccessController.doPrivileged( (PrivilegedAction) () -> { initialStartServices(cWaitMillis); markServicesStarted(); monitorServices(cWaitMillis); return null; }); } /** * Start the cache server and asynchronously check the service status * periodically, restarting any services that have stopped. * * @param cWaitMillis the number of milliseconds between checks * * @since Coherence 12.1.2 */ public void startDaemon(final long cWaitMillis) { while (true) { try { AccessController.doPrivileged(new DoAsAction( () -> { initialStartServices(cWaitMillis); return null; })); markServicesStarted(); break; } catch (Exception e) { // COH-2775: This can happen if some of the services // failed to start before main entered into its health // check loop. Since the startDaemon() is often called // during an application initialization logic when // running within a container, we should try to wait // a bit and do another attempt. In the worst case // they will see tons of restart attempts in the log. CacheFactory.log("Failed to start services: " + e, CacheFactory.LOG_ERR); Runtime.getRuntime().removeShutdownHook(m_threadShutdown); try { Blocking.sleep(DEFAULT_WAIT_MILLIS); } catch (InterruptedException e2) { // this is a daemon thread; we can ignore interrupts // why should we ignore an explicit interrupt even if // this is a daemon break; } } } } /** * Start all services that are declared as requiring an "autostart" in * the configurable factory XML descriptor. * * @return list of services that have been successfully started */ public List startServices() { return new LinkedList(startServicesInternal().keySet()); } /** * Shutdown the DefaultCacheServer. */ public void shutdownServer() { List listListener = ensureLifecycleListeners(); Context ctx = new LifecycleContext(); for (LifecycleListener listener : listListener) { try { listener.preStop(ctx); } catch (Throwable e) { CacheFactory.err(e); } } stopServiceMonitor(); CacheFactory.shutdown(); notifyShutdown(); for (LifecycleListener listener : listListener) { try { listener.postStop(ctx); } catch (Throwable e) { CacheFactory.err(e); } } // For a clean shutdown, remove the shutdown hook added // when the services are started to avoid leaking threads Runtime.getRuntime().removeShutdownHook(m_threadShutdown); } /** * Block the calling thread until DefaultCacheServer has called start on all * associated services. Upon control being returned services may not be running * due to possible error on start or concurrent shutdown. * * @throws IllegalStateException iff the services were not attempted to be * started, which could be due to concurrent shutdown */ public void waitForServiceStart() { if (!m_fServicesStarted) { synchronized (m_lock) { while (!(m_fServicesStarted || m_fShutdown)) { try { Blocking.wait(m_lock); } catch (InterruptedException e) {} } if (!m_fServicesStarted) { throw new IllegalStateException("Services were not started"); } } } } /** * Set the "services started" flag. */ protected void markServicesStarted() { synchronized (m_lock) { m_fServicesStarted = true; m_lock.notifyAll(); } } /** * Shutdown the cache server. * * @since Coherence 3.2 */ public static void shutdown() { DefaultCacheServer dcs = getInstance(); if (dcs != null) { s_instance = null; dcs.shutdownServer(); } } /** * Start the cache server on a dedicated daemon thread. This method is * intended to be used within managed containers. * * @since Coherence 3.2 * * @deprecated use {@link #startServerDaemon()} instead */ public static void startDaemon() { startServerDaemon(); } /** * Start the cache server on a dedicated daemon thread. This method is * intended to be used within managed containers. * * @return the instance of the {@link DefaultCacheServer} started * * @since Coherence 12.1.2 */ public static DefaultCacheServer startServerDaemon() { DefaultCacheServer server = ensureInstance(getConfigurableCacheFactory()); startDaemon(server); return server; } /** * Entry point: start the cache server under two possible contexts: *
    *
  1. With a "coherence-cache-config.xsd" compliant configuration file.
  2. *
  3. With a GAR file or a directory containing the contents of a GAR * file and optionally an application name and comma-delimited list of * tenant names.
  4. *
* If both are provided the latter takes precedence. Additionally * DefaultCacheServer accepts a numeric value to monitor the service * status (keep alive interval). *

* Default configuration file is "coherence-cache-config.xml"; default * keep alive interval is 5 sec. *

    * Example:
    *   java -server -Xms512m -Xmx512m com.tangosol.net.DefaultCacheServer cache-config.xml 5
    * GAR Example:
    *   java -server -Xms512m -Xmx512m com.tangosol.net.DefaultCacheServer my-app.gar MyApp 5
    * 
* * @param asArg the command line arguments */ public static void main(String[] asArg) { long cWaitMillis = DEFAULT_WAIT_MILLIS; File fileGar = null; String sAppName = null; String[] asTenant = null; for (int i = 0; i < asArg.length; ++i) { String sArg = asArg[i]; if (sArg.endsWith(".xml")) { CacheFactory.getCacheFactoryBuilder().setCacheConfiguration(null, XmlHelper.loadFileOrResource(sArg, "cache configuration", null)); } else if (sArg.equals("--version")) { System.out.println(CacheFactory.VERSION); if (asArg.length == 1) { System.exit(0); } } else if (Pattern.matches("[0-9]*", sArg)) { try { cWaitMillis = 1000L * Math.max(0, Integer.parseInt(sArg)); } catch (Exception e) {} } else if (sArg.endsWith(".gar") || sArg.contains(File.separator) || ".".equals(sArg)) { fileGar = new File(sArg); } else if (sArg.contains(",")) { asTenant = Base.parseDelimitedString(sArg, ','); } else { sAppName = sArg; } } DefaultCacheServer dcs; dcs = ensureInstance(getConfigurableCacheFactory()); if (asTenant != null) { CacheFactory.log("Multi-tenancy is only supported for a GAR deployment", CacheFactory.LOG_WARN); } dcs.startAndMonitor(cWaitMillis); } // ----- backward compatibility methods --------------------------------- /** * Start all services that are declared as requiring an "autostart" in * the default configurable factory XML descriptor. *

* This method will not create a ServiceMonitor for started services. * * @return list of services that have been successfully started */ public static List start() { return start(getConfigurableCacheFactory()); } /** * Start all services that are declared as requiring an "autostart" in * the configurable factory XML descriptor. *

* This method will not create a ServiceMonitor for started services. * * @param factory ConfigurableCacheFactory to use * * @return list of services that have been successfully started */ public static List start(ConfigurableCacheFactory factory) { return ensureInstance(factory).startServices(); } // ----- subclassing support methods ------------------------------------ /** * Ensures the DCS instance has shutdown and the associated * ServiceMonitor is no longer monitoring services. * * return true if the monitoring was active and has been stopped as a result * of this call; false if the monitoring was not active */ protected boolean stopMonitoring() { stopServiceMonitor(); return notifyShutdown(); } /** * Stop the ServiceMonitor. */ protected void stopServiceMonitor() { ServiceMonitor monitor = m_serviceMon; if (monitor != null) { monitor.stopMonitoring(); } } /** * Ensure the DCS instance has shutdown. * * return true if DCS instance was called to start on the associated services */ protected boolean notifyShutdown() { synchronized (m_lock) { boolean fWasStarted = m_fServicesStarted; m_fServicesStarted = false; m_fShutdown = true; m_lock.notifyAll(); return fWasStarted; } } /** * Setup any necessary resources and start all services. * * @param cWaitMillis the interval, in milliseconds, to monitor services */ protected void initialStartServices(long cWaitMillis) { Runtime.getRuntime().addShutdownHook(m_threadShutdown); m_serviceMon = new SimpleServiceMonitor(cWaitMillis); List listListener = ensureLifecycleListeners(); Context ctx = new LifecycleContext(); for (LifecycleListener listener : listListener) { try { listener.preStart(ctx); } catch (Throwable e) { CacheFactory.err(e); } } Map mapServices = startServicesInternal(); m_serviceMon.setConfigurableCacheFactory(m_factory); m_serviceMon.registerServices(mapServices); reportStarted(); for (LifecycleListener listener : listListener) { try { listener.postStart(ctx); } catch (Throwable e) { CacheFactory.err(e); } } } /** * Returns the list of {@link LifecycleListener}s registered for this {@link DefaultCacheServer}. *

* If the list of listeners does not yet exist it will be created and initially populated * using the {@link ServiceLoader} to discover and load listeners. * * @return the list of {@link LifecycleListener}s registered for this {@link DefaultCacheServer} */ protected List ensureLifecycleListeners() { if (m_listLifecycleListener == null) { synchronized (this) { List list = new CopyOnWriteArrayList<>(); ServiceLoader loader = ServiceLoader.load(LifecycleListener.class); for (LifecycleListener listener : loader) { list.add(listener); } m_listLifecycleListener = list; } } return m_listLifecycleListener; } /** * Log the start message. */ protected void reportStarted() { CacheFactory.log(getServiceBanner(CacheFactory.getCluster()) + '\n' + "Started " + getClass().getSimpleName() + "...\n", CacheFactory.LOG_INFO); } /** * Starts the services marked with autostart returning a map of a service * name to the started service. * * @return a map holding all started services keyed by names */ protected Map startServicesInternal() { ConfigurableCacheFactory factory = m_factory; Map mapServices = null; if (factory instanceof DefaultConfigurableCacheFactory) { DefaultConfigurableCacheFactory dccf = (DefaultConfigurableCacheFactory) factory; mapServices = new LinkedHashMap(); for (Object o : dccf.getConfig().getSafeElement("caching-schemes") .getElementList()) { XmlElement xmlScheme = (XmlElement) o; if (xmlScheme.getSafeElement("autostart").getBoolean()) { try { Service service = dccf.ensureService(xmlScheme); if (!mapServices.containsKey(service)) { mapServices.put(service, LegacyXmlServiceHelper.getServiceName(xmlScheme)); } } catch (RuntimeException e) { handleEnsureServiceException(e); } } } } else if (factory instanceof ExtensibleConfigurableCacheFactory) { ExtensibleConfigurableCacheFactory eccf = (ExtensibleConfigurableCacheFactory) factory; try { eccf.activate(); mapServices = eccf.getServiceMap(); } catch (RuntimeException e) { handleEnsureServiceException(e); } } else { throw new IllegalStateException("StartServices not supported for factory " + factory); } try { if (MetricsHttpHelper.isHttpCapable()) { MetricsHttpHelper.ensureMetricsService(mapServices); } } catch (Exception e) { err(e); } return mapServices; } /** * Check the ServiceMonitor status ensuring it is monitoring services. This * method blocks the caller's thread until the DefaultCacheServer is * shutdown and monitoring stops. * * @param cWaitMillis the number of milliseconds between checks */ protected void monitorServices(long cWaitMillis) { while (true) { synchronized (m_lock) { try { Blocking.wait(m_lock, cWaitMillis); } catch (InterruptedException e) { // we don't care because we are probably shutting down } } if (isMonitorStopped()) { stopMonitoring(); break; } } } /** * Invoked if starting a service raises an exception. Re-throws the * exception. Override, for example, to log the exception and continue * starting services. * * @param e the RuntimeException * * @see #startServices() */ protected void handleEnsureServiceException(RuntimeException e) { throw e; } /** * Create a list of running services in the given Cluster. * * @param cluster Cluster for which to create a list of running services * * @return string containing listing of running services */ protected String getServiceBanner(Cluster cluster) { // Extract the Cluster object out of the Safe layer to // de-clutter the logging try { cluster = (Cluster) ClassHelper. invoke(cluster, "getCluster", null); } catch (Exception e) { // If this fails for some reason, revert to SafeCluster } StringBuilder sb = new StringBuilder(); sb.append("\nServices\n (\n "); if (cluster != null) { for (Enumeration e = cluster.getServiceNames(); e.hasMoreElements();) { Service service = cluster.getService((String) e.nextElement()); if (service != null) { sb.append(service) .append("\n "); } } } sb.append(")\n"); return sb.toString(); } /** * Return the {@link ServiceMonitor} used by the cache server. * * @return the ServiceMonitor */ public boolean isMonitorStopped() { return !m_serviceMon.isMonitoring(); } // ----- helpers -------------------------------------------------------- /** * Start the provided cache server on a dedicated daemon thread. This * method is intended to be used within managed containers. * * @param dcs the cache server to start */ protected static void startDaemon(final DefaultCacheServer dcs) { Thread thread = makeThread(null, () -> dcs.startDaemon(DEFAULT_WAIT_MILLIS), "DefaultCacheServer"); thread.setDaemon(true); thread.start(); } /** * Gets a ConfigurableCacheFactory based on the default configuration. * * @return a ConfigurableCacheFactory */ protected static ConfigurableCacheFactory getConfigurableCacheFactory() { // we use getCCF() here instead of getCFB().getCCF() to be more // tolerant to possible prior setCCF() calls return CacheFactory.getConfigurableCacheFactory(); } /** * Returns the DefaultCacheServer singleton, creating it if necessary. * * @param factory the CacheFactory to use * * @return the DefaultCacheServer singleton */ protected static synchronized DefaultCacheServer ensureInstance(ConfigurableCacheFactory factory) { DefaultCacheServer instance = s_instance; if (instance == null) { s_instance = instance = new DefaultCacheServer(factory); } else if (factory != instance.m_factory) { throw new IllegalArgumentException( "The DefaultCacheServer has already been started with " + "a different factory object"); } return instance; } /** * Returns the DefaultCacheServer created by a previous invocation of * {@link #ensureInstance(ConfigurableCacheFactory)}. * Will throw an IllegalStateException if there is no instance. * * @return the DefaultCacheServer */ public static synchronized DefaultCacheServer getInstance() { DefaultCacheServer instance = s_instance; if (instance == null) { throw new IllegalStateException( "The DefaultCacheServer has not been started"); } return instance; } // ----- constants and data fields -------------------------------------- /** * The default number of milliseconds between checks for service restart. */ public static final long DEFAULT_WAIT_MILLIS = 5000; /** * The singleton instance of this class. */ private static DefaultCacheServer s_instance = null; /** * A lock used for synchronization of state on shutdown. */ private final Object m_lock = new Object(); /** * The ConfigurableCacheFactory used by this DefaultCacheServer. */ private final ConfigurableCacheFactory m_factory; /** * The ServiceMonitor used to monitor services. */ protected volatile ServiceMonitor m_serviceMon; /** * Flag that indicates whether of not the shutdown was initiated. */ protected volatile boolean m_fShutdown; /** * Flag that indicates whether this instance of DefaultCacheServer has * called start on the associated services. * * @see #initialStartServices */ protected boolean m_fServicesStarted; /** * Shutdown hook thread. */ protected Thread m_threadShutdown = makeThread(null, this::stopMonitoring, null); /** * The {@link LifecycleListener}s to be notified of lifecycle events * for this {@link DefaultCacheServer}. *

* The {@link LifecycleListener}s will be discovered and loaded using * the {@link java.util.ServiceLoader}. */ protected List m_listLifecycleListener; // ----- inner class: LifecycleContext ---------------------------------- /** * An implementation of {@link Context} that will be passed * to {@link DefaultCacheServer} {@link LifecycleListener}s. */ protected class LifecycleContext implements Context { @Override public ConfigurableCacheFactory getConfigurableCacheFactory() { return m_factory; } @Override public CacheFactoryBuilder getCacheFactoryBuilder() { return CacheFactory.getCacheFactoryBuilder(); } @Override public ClassLoader getClassLoader() { if (m_factory instanceof ExtensibleConfigurableCacheFactory) { ((ExtensibleConfigurableCacheFactory) m_factory).getConfigClassLoader(); } return m_factory.getClass().getClassLoader(); } @Override public String getApplicationName() { return "DefaultCacheServer"; } @Override public ServiceMonitor getServiceMonitor() { return m_serviceMon; } @Override public String getPofConfigURI() { return Config.getProperty("coherence.pof.config", "pof-config.xml"); } @Override public String getCacheConfigURI() { return Config.getProperty("coherence.cacheconfig", "coherence-cache-config.xml"); } @Override public ContainerContext getContainerContext() { return null; } @Override public ExtendedContext getExtendedContext() { return null; } } }