/** * Helios, OpenSource Monitoring * Brought to you by the Helios Development Group * * Copyright 2007, Helios Development Group and individual contributors * as indicated by the @author tags. See the copyright.txt file in the * distribution for a full listing of individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. * */ package org.helios.apmrouter.jmx; import org.w3c.dom.Node; import javax.management.ObjectName; import java.lang.Thread.UncaughtExceptionHandler; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; /** * <p>Title: ScheduledThreadPoolFactory</p> * <p>Description: Task scheduler</p> * <p>Company: Helios Development Group LLC</p> * @author Whitehead (nwhitehead AT heliosdev DOT org) * <p><code>org.helios.apmrouter.jmx.ScheduledThreadPoolFactory</code></p> */ public class ScheduledThreadPoolFactory extends ScheduledThreadPoolExecutor implements ThreadFactory, SchedulerMXBean, RejectedExecutionHandler, UncaughtExceptionHandler, TaskScheduler { /** The ObjectName that will be used to register the scheduled thread pool management interface */ protected final ObjectName objectName; /** Serial number factory for thread names */ protected final AtomicInteger serial = new AtomicInteger(0); /** The scheduler name */ protected final String name; /** Indicates if threads should be daemons */ protected final boolean daemonThreads; /** The currently scheduled tasks */ protected final Set<TrackedScheduledFuture> activeTasks = new CopyOnWriteArraySet<TrackedScheduledFuture>(); /** Externally scheduled task handles */ protected final Map<Long, TrackedScheduledFuture> externalTasks = new ConcurrentHashMap<Long, TrackedScheduledFuture>(); /** Serial number generator for external task handles */ protected final AtomicLong externalTaskSerial = new AtomicLong(0L); /** A map of created and started scheduler factories */ protected static final Map<String, ScheduledThreadPoolFactory> tpSchedulers = new ConcurrentHashMap<String, ScheduledThreadPoolFactory>(); /** The configuration node name */ public static final String NODE = "scheduler"; // /** Tab type for tasks */ // protected static final TabularType TASK_TAB_TYPE ; // // static { // try { // TASK_TAB_TYPE = new TabularType("ScheduledTasks", "All the scheduled tasks for this scheduler", TrackedScheduledFuture.COMPOSITE_TYPE, new String[]{"Id"}); // } catch (Exception e) { // throw new RuntimeException(e); // } // } /** * Returns the named scheduler * @param name The name of the scheduler to retrieve * @return The named scheduler * @throws IllegalStateException Thrown if the named scheduler does not exist */ public static ScheduledThreadPoolFactory getFullInstance(String name) { if(name==null) throw new IllegalArgumentException("The passed name was null", new Throwable()); ScheduledThreadPoolFactory tps = tpSchedulers.get(name); if(tps==null) throw new IllegalStateException("The scheduler named [" + name + "] has not been initialized" , new Throwable()); return tps; } /** * Returns the named scheduler * @param name The name of the scheduler to retrieve * @return The named scheduler * @throws IllegalStateException Thrown if the named scheduler does not exist */ public static TaskScheduler getInstance(String name) { return getFullInstance(name); } /** * Creates a new ScheduledThreadPoolFactory * @param tps A thread pool configuration */ protected ScheduledThreadPoolFactory(ScheduledThreadPoolConfig tps) { super(tps.coreSize); setContinueExistingPeriodicTasksAfterShutdownPolicy(false); setExecuteExistingDelayedTasksAfterShutdownPolicy(false); setRejectedExecutionHandler(this); setThreadFactory(this); name = tps.name; daemonThreads = tps.daemonThreads; objectName = JMXHelper.objectName("org.helios.apmrouter.jmx:service=Scheduler,name=" + name); JMXHelper.registerMBean(JMXHelper.getHeliosMBeanServer(), objectName, this); Thread shutdownThread = new Thread(){ public void run() { shutdownNow(); } }; Runtime.getRuntime().addShutdownHook(shutdownThread); } /** * Creates or retrieves a ScheduledThreadPoolFactory * @param configNode The XML configuration node * @return the ScheduledThreadPoolFactory named in the passed configNode */ public static ScheduledThreadPoolFactory newScheduler(Node configNode) { if(configNode==null) throw new IllegalArgumentException("Passed configuration node was null", new Throwable()); String nodeName = configNode.getNodeName(); if(!NODE.equals(nodeName)) { throw new RuntimeException("Configuration Node expected to have node name [" + NODE + "] but was [" + nodeName + "]", new Throwable()); } String poolName = XMLHelper.getAttributeByName(configNode, "name", null); if(poolName==null || poolName.trim().isEmpty()) { throw new RuntimeException("Scheduler Node had null name [" + XMLHelper.renderNode(configNode), new Throwable()); } ScheduledThreadPoolFactory tps = tpSchedulers.get(poolName); if(tps==null) { synchronized(tpSchedulers) { tps = tpSchedulers.get(poolName); if(tps==null) { tps = new ScheduledThreadPoolFactory(ScheduledThreadPoolConfig.getInstance(configNode)); tpSchedulers.put(poolName, tps); } } } return tps; } /** * Creates or retrieves a ScheduledThreadPoolFactory * @param name the name of the scheduler * @return the ScheduledThreadPoolFactory named in the passed configNode */ public static ScheduledThreadPoolFactory newScheduler(String name) { if(name==null || name.trim().isEmpty()) throw new IllegalArgumentException("Passed name was null or empty", new Throwable()); ScheduledThreadPoolFactory tps = tpSchedulers.get(name); if(tps==null) { synchronized(tpSchedulers) { tps = tpSchedulers.get(name); if(tps==null) { tps = new ScheduledThreadPoolFactory(ScheduledThreadPoolConfig.getInstance(name)); tpSchedulers.put(name, tps); } } } return tps; } /** * {@inheritDoc} * @see java.util.concurrent.ScheduledThreadPoolExecutor#decorateTask(java.lang.Runnable, java.util.concurrent.RunnableScheduledFuture) */ @Override protected <V> RunnableScheduledFuture<V> decorateTask(Runnable runnable, RunnableScheduledFuture<V> task) { return new ListenerAwareRunnableScheduledFuture<V>(task); } // /** // * Post execution hook // * @param r The runnable // * @param t The throwable // */ // @Override // protected void afterExecute(Runnable r, Throwable t) { // if(r instanceof TrackedScheduledFuture) { // activeTasks.remove(r); // } // super.afterExecute(r, t); // } /** * Returns the number of pending tasks * @return the number of pending tasks */ @Override public int getPendingTaskCount() { return activeTasks.size(); } /** * Enlists the tracked task * @param description The description of the task * @param delayPeriod The task delay or period in seconds * @param task The task to enlist * @return The enlisted task */ @SuppressWarnings("rawtypes") protected TrackedScheduledFuture enlistTask(String description, long delayPeriod, ScheduledFuture<?> task) { ListenerAwareRunnableScheduledFuture sf = (ListenerAwareRunnableScheduledFuture)task; final TrackedScheduledFuture trackedTask = new TrackedScheduledFuture(sf, description, delayPeriod, activeTasks); sf.addCompletionListener(new Runnable() { @Override public void run() { activeTasks.remove(trackedTask); } }); activeTasks.add(trackedTask); return trackedTask; } /** * Creates and executes a one-shot action that becomes enabled after the given delay. * @param description A description of the command * @param command The runnable to schedule * @param delay The delay time * @param unit The delay unit * @return the scheduled future for the task */ @Override public TrackedScheduledFuture schedule(String description, Runnable command, long delay, TimeUnit unit) { return enlistTask(description, TimeUnit.SECONDS.convert(delay, unit), schedule(command, delay, unit)); } /** * Creates and executes a one-shot action that becomes enabled after the given delay. * @param description A description of the command * @param callable The callable to schedule * @param delay The delay time * @param unit The delay unit * @return the scheduled future for the task */ @Override public TrackedScheduledFuture schedule(String description, Callable<?> callable, long delay, TimeUnit unit) { return enlistTask(description, TimeUnit.SECONDS.convert(delay, unit), super.schedule(callable, delay, unit)); } /** * Creates and executes a periodic action that becomes enabled first after the given initial delay, * and subsequently with the given period; that is executions will commence after initialDelay * then initialDelay+period, then initialDelay + 2 * period, and so on. * @param description A description of the command * @param command The command to schedule * @param initialDelay the time to delay first execution * @param period the period between successive executions * @param unit The period unit * @return the scheduled future for the task */ @Override public TrackedScheduledFuture scheduleAtFixedRate(String description, Runnable command, long initialDelay, long period, TimeUnit unit) { return enlistTask(description, TimeUnit.SECONDS.convert(period, unit), super.scheduleAtFixedRate(command, initialDelay, period, unit)); } /** * Creates and executes a periodic action that becomes enabled first after the given initial delay, * and subsequently with the given delay between the termination of one execution and the commencement of the next. * If any execution of the task encounters an exception, subsequent executions are suppressed. Otherwise, the task will only terminate via cancellation or termination of the executor. * @param description A description of the command * @param command The command to schedule * @param initialDelay the time to delay first execution * @param period the period between successive executions * @param unit The period unit * @return the scheduled future for the task */ @Override public TrackedScheduledFuture scheduleWithFixedDelay(String description, Runnable command, long initialDelay, long period, TimeUnit unit) { return enlistTask(description, TimeUnit.SECONDS.convert(period, unit), super.scheduleWithFixedDelay(command, initialDelay, period, unit)); } /** * Creates and executes a periodic action that becomes enabled first after the given initial delay, * and subsequently with the given delay between the termination of one execution and the commencement of the next. * If any execution of the task encounters an exception, subsequent executions are suppressed. Otherwise, the task will only terminate via cancellation or termination of the executor. * @param description A description of the command * @param task The ObjectName of the task to schedule * @param initialDelay the time to delay first execution * @param period the period between successive executions * @param unit The period unit * @return the task schedule handle use to cancel the task */ @Override public long scheduleWithFixedDelay(String description, final ObjectName task, final long initialDelay, final long period, final String unit) { TrackedScheduledFuture tsf = enlistTask(description, TimeUnit.SECONDS.convert(period, TimeUnit.valueOf(unit)), super.scheduleWithFixedDelay(new Runnable(){ @Override public void run() { JMXHelper.invoke(task, JMXHelper.getHeliosMBeanServer(), "run", new Object[]{}, new String[]{}); } }, initialDelay, period, TimeUnit.valueOf(unit))); long serial = externalTaskSerial.incrementAndGet(); externalTasks.put(serial, tsf); return serial; } /** * Cancels the task associated with the passed handle * @param taskId The task handle * @param mayInterruptIfRunning true if the task can be interrupted if running */ public void cancelTask(long taskId, boolean mayInterruptIfRunning) { TrackedScheduledFuture tsf = externalTasks.remove(taskId); if(tsf!=null) tsf.cancel(mayInterruptIfRunning); } /** * Returns an array of the scheduled tasks * @return an array of the scheduled tasks */ @Override public Set<TrackedScheduledFuture> getScheduledTasks() { return new HashSet<TrackedScheduledFuture>(activeTasks); } /** * Returns the assigned JMX ObjectName * @return the objectName */ public ObjectName getObjectName() { return objectName; } /** * Returns the scheduler name * @return the name */ public String getName() { return name; } /** * {@inheritDoc} * @see java.util.concurrent.RejectedExecutionHandler#rejectedExecution(java.lang.Runnable, java.util.concurrent.ThreadPoolExecutor) */ @Override public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) { System.err.println("Scheduler rejected execution of [" + r + "]"); } /** * {@inheritDoc} * @see java.lang.Thread.UncaughtExceptionHandler#uncaughtException(java.lang.Thread, java.lang.Throwable) */ @Override public void uncaughtException(Thread t, Throwable e) { System.err.println("Scheduler had uncaught exception. Stack trace follows."); e.printStackTrace(System.err); } /** * {@inheritDoc} * @see java.util.concurrent.ThreadFactory#newThread(java.lang.Runnable) */ @Override public Thread newThread(Runnable r) { Thread t = new Thread(r, name + "SchedulerThread#" + serial.incrementAndGet()); t.setDaemon(true); t.setUncaughtExceptionHandler(this); return t; } /** * <p>Title: ScheduledThreadPoolConfig</p> * <p>Description: Value container and parser for a scheduled thread pool config</p> * <p>Company: Helios Development Group LLC</p> * @author Whitehead (nwhitehead AT heliosdev DOT org) * <p><code>org.helios.apmrouter.jmx.ScheduledThreadPoolFactory.ScheduledThreadPoolConfig</code></p> */ protected static class ScheduledThreadPoolConfig { /** The name of the pool */ protected final String name; /** The pool's core thread count */ protected final int coreSize; /** The maximum amount of time allowed for threads to finish their work on a non-immediate shutdown */ protected final long termTime; /** Indicates if the pool should be terminated immdeiately on shutdown notice */ protected final boolean immediateTerm; /** Indicates if threads should be daemons */ protected final boolean daemonThreads; /** The default core size which is 1 */ public static final int DEF_CORE_SIZE = 1; /** The default max size which is the number of core available X 4 */ public static final int DEF_MAX_SIZE = DEF_CORE_SIZE; /** The default keep alive time for idles threads which is 60s */ public static final long DEF_KEEPALIVE = 60; /** The default daemon status of pool threads which is true */ public static final boolean DEF_DAEMON = true; /** The default immediate termination which is true */ public static final boolean DEF_IMMEDIATE_TERM = true; /** The default allowed termination time which is 5 s */ public static final long DEF_TERM_TIME = 5; /** * Creates a new ScheduledThreadPoolConfig with a default configurtation * @param name The name of the scheduler to create */ private ScheduledThreadPoolConfig(String name) { this.name = name; coreSize = DEF_CORE_SIZE; termTime = DEF_TERM_TIME; immediateTerm = DEF_IMMEDIATE_TERM; daemonThreads = DEF_DAEMON; } /** * Creates a new ScheduledThreadPoolConfig * @param configNode The configuration node */ public ScheduledThreadPoolConfig(Node configNode) { if(configNode==null) throw new RuntimeException("Passed configuration node was null", new Throwable()); String nodeName = configNode.getNodeName(); if(!NODE.equals(nodeName)) { throw new RuntimeException("Configuration Node expected to have node name [" + NODE + "] but was [" + nodeName + "]", new Throwable()); } name = XMLHelper.getAttributeByName(configNode, "name", null); if(name==null || name.trim().isEmpty()) { throw new RuntimeException("ThreadPool Node had null name [" + XMLHelper.renderNode(configNode), new Throwable()); } // ===== Pool Stuff ===== Node currentNode = XMLHelper.getChildNodeByName(configNode, "pool", false); coreSize = XMLHelper.getAttributeByName(currentNode, "core", DEF_CORE_SIZE); daemonThreads = XMLHelper.getAttributeByName(currentNode, "daemon", DEF_DAEMON); // ===== Termination Stuff ===== currentNode = XMLHelper.getChildNodeByName(configNode, "termination", false); immediateTerm = XMLHelper.getAttributeByName(currentNode, "immediate", DEF_IMMEDIATE_TERM); if(immediateTerm) { termTime = 0; } else { termTime = XMLHelper.getAttributeByName(currentNode, "termTime", DEF_TERM_TIME); } } /** * Returns a ScheduledThreadPoolConfig for the passed config node * @param configNode The configuration node * @return a ThreadPoolConfig for the passed config node */ static ScheduledThreadPoolConfig getInstance(Node configNode) { return new ScheduledThreadPoolConfig(configNode); } /** * Creates a default configuration scheduler config * @param name The name of the scheduler to create * @return the named default configuration */ static ScheduledThreadPoolConfig getInstance(String name) { return new ScheduledThreadPoolConfig(name); } /** * Returns the name of the pool * @return the name */ public String getName() { return name; } /** * Returns the core pool size * @return the coreSize */ public int getCoreSize() { return coreSize; } /** * Returns the amount of time in s. that threads will be given to complete their tasks on a shutdown notice. * Not relevant if {@link #immediateTerm} is true. * @return the termTime */ public long getTermTime() { return termTime; } /** * Indicates if this pool will be shutdown immediately on a shutdown notice * @return the immediateTerm */ public boolean isImmediateTerm() { return immediateTerm; } /** * {@inheritDoc} * @see java.lang.Object#toString() */ @Override public String toString() { StringBuilder builder = new StringBuilder(); builder.append("ScheduledThreadPoolConfig [name=").append(name) .append(", coreSize=").append(coreSize) .append(", termTime=").append(termTime) .append(", immediateTerm=").append(immediateTerm); return builder.toString(); } } }