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

com.tangosol.util.Daemon Maven / Gradle / Ivy

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

package com.tangosol.util;


import com.tangosol.net.Guardable;
import com.tangosol.net.Guardian;

import com.oracle.coherence.common.base.Blocking;
import com.tangosol.net.Guardian.GuardContext;
import com.tangosol.net.GuardSupport;


/**
* A abstract Daemon thread handler. A sub-class need only provide an
* implementation of the run() method. When the Daemon is told to start, it
* will create a Java thread, which means that the number of Daemon instances
* within a system must be limited to a reasonable number. If an arbitrarily
* large number of conceptual daemons are necessary, consider using a
* TaskDaemon, which allows arbitrary tasks to be queued for execution.
*
* @author cp  2000.08.02
* @author cp  2006.02.23 (Coherence 3.2) added restartability
* @author jh  2010.05.07 (Coherence 3.6) added shutdown
*/
public abstract class Daemon
        extends Base
        implements Runnable, Guardable
    {
    // ----- constructors ---------------------------------------------------

    /**
    * Default constructor.  Creates a Daemon using default settings. The
    * daemon will not be automatically started.
    */
    public Daemon()
        {
        this(null);
        }

    /**
    * Creates a Daemon with the specified name. The daemon will not be
    * automatically started.
    *
    * @param sName  the thread name (may be null)
    */
    public Daemon(String sName)
        {
        this(sName, Thread.NORM_PRIORITY, false);
        }

    /**
    * Creates a Daemon with a specified name and priority.
    * 

* Warning: If the implementation class is a inner non-static * class that refers to its outer object ("MyOuterClass.this"), do not * use the auto-start option. The reason is that the run() method of the * Daemon may be invoked on the new thread before the construction of * the Daemon object itself unwinds, which means that the outer object * reference will not have been set yet, causing an inexplicable * NullPointerException. * * @param sName the thread name (may be null) * @param nPriority the thread priority, between Thread.MIN_PRIORITY and * Thread.MAX_PRIORITY inclusive * @param fStart pass true to auto-start the thread as part of its * construction */ public Daemon(String sName, int nPriority, boolean fStart) { setConfiguredName(sName); setConfiguredPriority(nPriority); // auto-start the daemon if requested to if (fStart) { start(); } } // ----- Runnable interface --------------------------------------------- /** * The daemon's implementation method. Override this method to implement * a daemon. *

* An example implementation is: *

{@code
    *   while (!isStopping())
    *       {
    *       // do some processing
    *       // ...
    *
    *       synchronized (this)
    *           {
    *           // wait for notification of more work
    *           wait();
    *           }
    *       }
    * }
*/ public abstract void run(); // ----- Guardable interface -------------------------------------------- /** * {@inheritDoc} */ public void setContext(Guardian.GuardContext context) { m_guardContext = context; } /** * {@inheritDoc} */ public void recover() { Thread thread = getThread(); if (thread != null) { thread.interrupt(); } } /** * {@inheritDoc} */ public void terminate() { stop(); } /** * {@inheritDoc} */ public Guardian.GuardContext getContext() { return m_guardContext; } // ----- Guardian-related helpers --------------------------------------- /** * Return true iff this Daemon's execution is guarded. See {@link * com.tangosol.net.Guardian Guardian}. * * @return true iff this Daemon is guarded */ public boolean isGuarded() { return m_guardContext != null; } /** * If this Daemon is guarded, return the maximum wait time that the Daemon * is allowed to wait for, according to its SLA. * * @param cDefaultMillis the default wait time to return if this Daemon * is not Guarded * @return the maximum wait time allowed, or cDefaultMillis if this Daemon * is not guarded */ public long getMaxWaitMillis(long cDefaultMillis) { long cMaxWaitMillis = cDefaultMillis; Guardian.GuardContext context = getContext(); if (context != null) { // Adjust the timeout given by the guard contract; only // wait for a fraction of that time between heartbeats long cTimeoutMillis = context.getSoftTimeoutMillis(); cMaxWaitMillis = Math.min(1000L, Math.max(1L, cTimeoutMillis >> 2)); } return cMaxWaitMillis; } /** * If this Daemon is being guarded, issue a heartbeat to the Guardian. */ protected void heartbeat() { GuardContext context = getContext(); if (context != null) { context.heartbeat(); } } /** * If this Daemon is being guarded, issue a heartbeat to the Guardian. * * @param cMillis the number of milliseconds for which this Daemon * should not be considered timed out */ protected void heartbeat(long cMillis) { GuardContext context = getContext(); if (context != null) { context.heartbeat(cMillis); } } /** * Set the Guardian registration action. This action is invoked when a new * DaemonWorker thread is started to register the daemon with its Guardian. * * @param action the Guardian registration action */ protected void setGuardRegisterAction(Runnable action) { m_actionGuardRegister = action; } /** * Return the Guardian registration action. * * @return the Guardian registration action */ protected Runnable getGuardRegisterAction() { return m_actionGuardRegister; } /** * Set the Guardian and policy to guard this Daemon with. The Daemon is * registered with the specified Guardian each time the Daemon is started, * and is released each time the Daemon is stopped. * * @param guardian the Guardian that will be guarding this Daemon * @param cTimeoutMillis the timeout in ms for this Daemon, or 0 for the * service guardian timeout * @param flPctRecover the recovery percentage for this Daemon */ protected void setGuardPolicy(final Guardian guardian, final long cTimeoutMillis, final float flPctRecover) { // if the Daemon is already guarded, remove the old guard policy GuardContext ctx = getContext(); if (ctx != null) { ctx.release(); } // capture the Guardian and SLA in a registration thunk setGuardRegisterAction(new Runnable() { public void run() { if (cTimeoutMillis == 0) { guardian.guard(Daemon.this); } else { guardian.guard(Daemon.this, cTimeoutMillis, flPctRecover); } } }); if (isRunning()) { // if the Daemon is running, enforce the new guard policy guardIfNeeded(); } } /** * If this Daemon has a Guardian and SLA policy specified, ensure that it * is registered with its Guardian. */ protected void guardIfNeeded() { Runnable actionGuard = getGuardRegisterAction(); if (actionGuard != null) { actionGuard.run(); } } // ----- Thread-related ------------------------------------------------- /** * Accessor to obtain the Daemon thread object. *

* The thread returned by this accessor will be null if the Daemon is * stopped. * * @return the thread object */ public Thread getThread() { DaemonWorker worker = getWorker(); return worker == null ? null : worker.getThread(); } /** * Accessor to obtain the Daemon worker object. *

* The worker returned by this accessor will be null if the Daemon is * stopped. * * @return the worker object */ public DaemonWorker getWorker() { return m_worker; } /** * Performs a synchronized start of the thread if the thread is not * already started. This means that when control returns to the caller * of this method, that the thread has started. This method has no effect * if the daemon thread is already running, even if it is in the process * of stopping. Note that a daemon can be re-started once it has stopped. */ public synchronized void start() { DaemonWorker worker = getWorker(); if (worker != null && worker.isCurrentThread()) { // what does it mean when a daemon thread asks itself to // start? it must be started if it's calling this method, // right? return; } if (worker != null && !worker.getThread().isAlive()) { // clean up from a failed thread changeState(STATE_STOPPED, worker); } guardIfNeeded(); switch (getState()) { default: case STATE_STOPPED: worker = instantiateWorker(); configureWorker(worker); changeState(STATE_STARTING, worker); worker.getThread().start(); // fall through case STATE_STARTING: azzert(worker != null); // wait for the thread to finish starting finishStarting(worker); return; case STATE_RUNNING: return; case STATE_STOPPING: // this is a strange situation, since the thread is // going to stop but it hasn't yet, so it is still // running, so consider the start to be successful } } /** * Check if the daemon is running (has started and has not stopped). * * @return true if and only if the daemon is running */ public boolean isRunning() { switch (getState()) { default: case STATE_STOPPED: // not running case STATE_STARTING: // not yet started return false; case STATE_RUNNING: // running case STATE_STOPPING: // not yet stopped running Thread thread = getThread(); return thread != null && thread.isAlive(); } } /** * Request the daemon to stop. This method will only have an effect if the * daemon sub-class respects the value returned from {@link #isStopping()}. */ public synchronized void stop() { DaemonWorker worker = getWorker(); if (worker != null && worker.isCurrentThread()) { // stop called on self immediately marks the daemon as // having been stopped changeState(STATE_STOPPED, worker); } else if (isOnWorkerThread()) { // this is being called from a daemon worker thread that is // already "stopped" from the client point of view } else if (worker != null && !worker.getThread().isAlive()) { // the thread died already changeState(STATE_STOPPED, worker); } else { switch (getState()) { case STATE_STARTING: // wait for the thread to finish starting finishStarting(worker); if (getState() != STATE_RUNNING || worker != getWorker()) { // while we were waiting in finishStarting(), the worker // thread has skipped the RUNNING state, which means // that it is stopping or has already stopped return; } // fall through case STATE_RUNNING: changeState(STATE_STOPPING, worker); return; case STATE_STOPPING: // already stopping case STATE_STOPPED: // already stopped } } } /** * Request the daemon to stop and wait up to the specified number of * milliseconds for it to exit. This method will only have an effect if * the daemon sub-class respects the value returned from * {@link #isStopping()}. * * @param cWait the maximum number of milliseconds to wait for the * daemon to exit; pass zero to return immediately; * pass -1 to block the calling thread until the worker * finishes stopping */ public synchronized void shutdown(long cWait) { if (isOnWorkerThread()) { throw new IllegalStateException( "shutdown cannot be called by a daemon thread"); } else { stop(); } if (getState() == STATE_STOPPING) { finishStopping(getWorker(), cWait); } } /** * Check if the daemon is supposed to stop. This method is primarily used * by the daemon thread itself to check if it should stop processing. * * @return true if and only if the worker thread is no longer supposed * to be running */ public boolean isStopping() { switch (getState()) { case STATE_STOPPING: return true; case STATE_STOPPED: // normally we return false here, but in the case where the // caller is a child thread, then the result is true as // from its point of view it is still stopping. This state // can be reached when a daemon calls stop on itself, in which // case it immediately goes into the stopped state, but has // to still think it is stopping until the thread dies. return isOnWorkerThread(); default: return false; } } /** * Indicate if the current execution thread is a child of this daemon. * * @return true if the current thread is a child of the Daemon */ public boolean isOnWorkerThread() { return m_threadGroup == Thread.currentThread().getThreadGroup(); } /** * Obtain the state of the daemon. * * @return one of the STATE_enums */ protected int getState() { return m_nState; } /** * Wait for the specified worker to finish starting. This method is called * while the daemon is in the STARTING state. The caller must be * synchronized on this daemon object. * * @param worker a worker created by this daemon */ protected void finishStarting(DaemonWorker worker) { while (worker == getWorker() && worker.getThread().isAlive() && getState() == STATE_STARTING) { try { // wait for the daemon worker thread to finish starting // (be aware that _anything_ can change while we are waiting) Blocking.wait(this, 1000); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw ensureRuntimeException(e); } } } /** * Wait for the specified worker to finish stopping. This method is called * while the daemon is in the STOPPING state. The caller must be * synchronized on this daemon object. * * @param worker a worker created by this daemon * @param cWait the maximum number of milliseconds to wait for the * specified worker to finish stopping; pass zero to return * immediately; pass -1 to block the calling thread until * the worker finishes stopping */ protected void finishStopping(DaemonWorker worker, long cWait) { long ldtStart = getSafeTimeMillis(); long ldtNow = ldtStart; long ldtStop = cWait < 0 ? Long.MAX_VALUE : cWait == 0 ? ldtStart : ldtStart + cWait; while (worker == getWorker() && worker.getThread().isAlive() && getState() == STATE_STOPPING) { try { if (ldtNow >= ldtStop) { return; } Blocking.wait(this, ldtStop - ldtNow); ldtNow = getSafeTimeMillis(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw ensureRuntimeException(e); } } } /** * Change the state of the daemon. * * @param nState one of the STATE_enums * @param worker the new worker, if starting, otherwise the current * worker */ protected synchronized void changeState(int nState, DaemonWorker worker) { DaemonWorker workerPrev = getWorker(); switch (nState) { case STATE_STARTING: if (workerPrev != null) { err("unexpected state: a thread (" + worker + ") is starting while another thread (" + workerPrev + ") still exists"); } m_worker = worker; m_nState = STATE_STARTING; break; case STATE_RUNNING: if (worker == workerPrev) { m_nState = STATE_RUNNING; if (isGuarded()) { // set the guardian context for the current thread GuardSupport.setThreadContext(getContext()); } } break; case STATE_STOPPING: if (worker == workerPrev) { worker.notifyStopping(); m_nState = STATE_STOPPING; } break; case STATE_STOPPED: if (worker == workerPrev) { worker.notifyStopping(); m_worker = null; m_nState = STATE_STOPPED; // release the GuardContext if this Daemon is being guarded if (isGuarded()) { getContext().release(); GuardSupport.setThreadContext(null); } } break; } notifyAll(); } // ----- daemon configuration ------------------------------------------- /** * Configure the priority for the daemon. * * @param nPriority the thread priority for the daemon */ protected void setConfiguredPriority(int nPriority) { nPriority = Math.min(nPriority, Thread.MAX_PRIORITY); nPriority = Math.max(nPriority, Thread.MIN_PRIORITY); m_nConfiguredPriority = nPriority; } /** * Determine the configured priority for the daemon. * * @return the configured thread priority for the daemon */ protected int getConfiguredPriority() { return m_nConfiguredPriority; } /** * Configure the name for the daemon. * * @param sName the thread name for the daemon */ protected void setConfiguredName(String sName) { m_sConfiguredName = sName; } /** * Determine the configured name for the daemon. * * @return the configured thread name for the daemon */ protected String getConfiguredName() { return m_sConfiguredName; } /** * Configure the context ClassLoader for the daemon thread. *

* If the daemon thread is not currently running, the specified ClassLoader * will be associated with the daemon thread when it is started (or * restarted). Otherwise, {@link Thread#setContextClassLoader(ClassLoader) * setContextClassLoader} will be called on the daemon thread object * immediately as well as during subsequent restarts. * * @param loader the context ClassLoader for the daemon thread */ public void setThreadContextClassLoader(ClassLoader loader) { Thread thread = getThread(); if (thread != null) { thread.setContextClassLoader(loader); } m_loaderConfigured = loader; } /** * Determine the configured context ClassLoader for the daemon thread. * * @return the configured context ClassLoader for the daemon thread */ public ClassLoader getThreadContextClassLoader() { return m_loaderConfigured; } // ----- inner class: DaemonWorker -------------------------------------- /** * Instantiate a DaemonWorker that will be used as a daemon. * * @return a new instance of DaemonWorker or a sub-class thereof */ protected DaemonWorker instantiateWorker() { return new DaemonWorker(); } /** * Configure a worker to use as a daemon. * * @param worker the DaemonWorker to configure */ protected void configureWorker(DaemonWorker worker) { Thread threadWorker; ThreadGroup curThreadGroup = ensureThreadGroup(); synchronized (curThreadGroup) // ensures that the thread group is not destroyed concurrently { if (curThreadGroup.isDestroyed()) { ensureThreadGroup(); } threadWorker = makeThread(m_threadGroup, worker, null); } threadWorker.setDaemon(true); threadWorker.setPriority(getConfiguredPriority()); String sName = getConfiguredName(); if (sName != null) { threadWorker.setName(sName); } ClassLoader loader = getThreadContextClassLoader(); if (loader != null) { threadWorker.setContextClassLoader(loader); } worker.setThread(threadWorker); } /** * Obtain the existing ThreadGroup or create one if none exists or the * current one is destroyed. * * @return the ThreadGroup that the worker will be part of */ protected ThreadGroup ensureThreadGroup() { ThreadGroup threadGroup = m_threadGroup; if (threadGroup == null || threadGroup.isDestroyed()) { threadGroup = m_threadGroup = new ThreadGroup(getConfiguredName()); // Make it a daemon so that it is destroyed automatically. threadGroup.setDaemon(true); } return threadGroup; } /** * The sub-class of Thread that this Daemon uses as the actual thread * of execution. */ public class DaemonWorker implements Runnable { public void run() { Daemon daemon = getDaemon(); azzert(daemon != null); try { daemon.changeState(STATE_RUNNING, this); daemon.run(); } finally { daemon.changeState(STATE_STOPPED, this); } // when this method returns, the daemon thread terminates } protected Daemon getDaemon() { return Daemon.this; } protected void notifyStopping() { m_fStopping = true; } protected boolean isStopping() { return m_fStopping; } protected void setThread(Thread thread) { m_thread = thread; } protected Thread getThread() { return m_thread; } protected boolean isCurrentThread() { return Thread.currentThread() == getThread(); } private volatile boolean m_fStopping; private Thread m_thread; } // ----- logging support ------------------------------------------------ /** * Return a human-readable String representation of the Daemon. * * @return a String describing the Daemon */ public String toString() { return "Daemon{" + getDescription() + '}'; } /** * Format the Daemon attributes into a String for inclusion in the String * returned from the {@link #toString} method. * * @return a String listing the attributes of the Daemon */ protected String getDescription() { return "Thread=\"" + getThread() + '\"' + ", State=" + toStateString(getState()); } /** * Convert a state value to a human-readable String. * * @param nState a Daemon state, one of the STATE_* enums * * @return a human-readable name for the state */ protected static String toStateString(int nState) { switch (nState) { case STATE_STARTING: return "Starting"; case STATE_RUNNING: return "Running"; case STATE_STOPPING: return "Stopping"; case STATE_STOPPED: return "Stopped"; default: return "Unknown"; } } // ----- constants ------------------------------------------------------ /** * State: Dormant, not running, no thread, etc. */ private static final int STATE_STOPPED = 0; /** * State: Starting up a daemon thread. */ private static final int STATE_STARTING = 1; /** * State: Daemon thread is running. */ private static final int STATE_RUNNING = 2; /** * State: Daemon thread is running, but has been told to stop and thus * will likely soon be stopped. */ private static final int STATE_STOPPING = 3; // ----- data members --------------------------------------------------- /** * The name configured for the daemon thread. */ private String m_sConfiguredName; /** * The priority configured for the daemon thread. */ private int m_nConfiguredPriority; /** * The context ClassLoader configured for the daemon thread. */ private ClassLoader m_loaderConfigured; /** * The DaemonWorker object. */ private volatile DaemonWorker m_worker; /** * State of the daemon. One of the STATE_* enums. */ private volatile int m_nState; /** * ThreadGroup used to identify child workers. */ private ThreadGroup m_threadGroup; /** * Action to register this Daemon with its Guardian. */ private Runnable m_actionGuardRegister; /** * The GuardContext guarding this Guardable. */ private Guardian.GuardContext m_guardContext; }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy