/******************************************************************************* * This file is part of OpenNMS(R). * * Copyright (C) 2006-2011 The OpenNMS Group, Inc. * OpenNMS(R) is Copyright (C) 1999-2011 The OpenNMS Group, Inc. * * OpenNMS(R) is a registered trademark of The OpenNMS Group, Inc. * * OpenNMS(R) is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * by the Free Software Foundation, either version 3 of the License, * or (at your option) any later version. * * OpenNMS(R) 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OpenNMS(R). If not, see: * http://www.gnu.org/licenses/ * * For more information contact: * OpenNMS(R) Licensing <license@opennms.org> * http://www.opennms.org/ * http://www.opennms.com/ *******************************************************************************/ package org.opennms.netmgt.scheduler; import java.lang.reflect.UndeclaredThrowableException; import java.util.Map; import java.util.Map.Entry; import java.util.concurrent.ConcurrentSkipListMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.RejectedExecutionException; import org.opennms.core.concurrent.LogPreservingThreadFactory; import org.opennms.core.fiber.PausableFiber; import org.opennms.core.queue.FifoQueueImpl; import org.opennms.core.utils.ThreadCategory; import org.springframework.util.Assert; /** * This class implements a simple scheduler to ensure the polling occurs at the * expected intervals. The scheduler employees a dynamic thread pool that adjust * to the load until a maximum thread count is reached. * * @author <a href="mailto:mike@opennms.org">Mike Davidson </a> * @author <a href="mailto:weave@oculan.com">Brian Weaver </a> * @author <a href="http://www.opennms.org/">OpenNMS </a> */ public class LegacyScheduler implements Runnable, PausableFiber, Scheduler { /** * The map of queue that contain {@link ReadyRunnable ready runnable} * instances. The queues are mapped according to the interval of scheduling. */ private Map<Long, PeekableFifoQueue<ReadyRunnable>> m_queues; /** * The total number of elements currently scheduled. This should be the sum * of all the elements in the various queues. */ private int m_scheduled; /** * The pool of threads that are used to executed the runnable instances * scheduled by the class' instance. */ private ExecutorService m_runner; /** * The status for this fiber. */ private int m_status; /** * The worker thread that executes this instance. */ private Thread m_worker; /** * This queue extends the standard FIFO queue instance so that it is * possible to peek at an instance without removing it from the queue. * */ public static final class PeekableFifoQueue<T> extends FifoQueueImpl<T> { /** * This method allows the caller to peek at the next object that would * be returned on a <code>remove</code> call. If the queue is * currently empty then the caller is blocked until an object is put * into the queue. * * @return The object that would be returned on the next call to * <code>remove</code>. * * @throws java.lang.InterruptedException * Thrown if the thread is interrupted. * @throws org.opennms.core.queue.FifoQueueException * Thrown if an error occurs removing an item from the * queue. */ public T peek() throws InterruptedException { return m_delegate.peek(); } } /** * Constructs a new instance of the scheduler. The maximum number of * executable threads is specified in the constructor. The executable * threads are part of a runnable thread pool where the scheduled runnables * are executed. * * @param parent * String prepended to "Scheduler" to create fiber name * @param maxSize * The maximum size of the thread pool. */ public LegacyScheduler(String parent, int maxSize) { m_status = START_PENDING; m_runner = Executors.newFixedThreadPool( maxSize, new LogPreservingThreadFactory(getClass().getSimpleName(), maxSize, false) ); m_queues = new ConcurrentSkipListMap<Long, PeekableFifoQueue<ReadyRunnable>>(); m_scheduled = 0; m_worker = null; } /** * Constructs a new instance of the scheduler. The maximum number of * executable threads is specified in the constructor. The executable * threads are part of a runnable thread pool where the scheduled runnables * are executed. * * @param parent * String prepended to "Scheduler" to create fiber name * @param maxSize * The maximum size of the thread pool. * @param lowMark * The low water mark ratios of thread size to threads when * threads are stopped. * @param hiMark * The high water mark ratio of thread size to threads when * threads are started. */ public LegacyScheduler(String parent, int maxSize, float lowMark, float hiMark) { this(parent, maxSize); } /** * This method is used to schedule a ready runnable in the system. The * interval is used as the key for determining which queue to add the * runnable. * * @param runnable * The element to run when interval expires. * @param interval * The queue to add the runnable to. * @throws java.lang.RuntimeException * Thrown if an error occurs adding the element to the queue. */ public synchronized void schedule(ReadyRunnable runnable, long interval) { if (log().isDebugEnabled()) { log().debug("schedule: Adding ready runnable "+runnable+" at interval " + interval); } Long key = new Long(interval); if (!m_queues.containsKey(key)) { if (log().isDebugEnabled()) { log().debug("schedule: interval queue did not exist, a new one has been created"); } m_queues.put(key, new PeekableFifoQueue<ReadyRunnable>()); } try { m_queues.get(key).add(runnable); if (m_scheduled++ == 0) { log().debug("schedule: queue element added, calling notify all since none were scheduled"); notifyAll(); } else if (log().isDebugEnabled()) { log().debug("schedule: queue element added, notification not performed"); } } catch (InterruptedException e) { log().info("schedule: failed to add new ready runnable instance " + runnable + " to scheduler: " + e, e); Thread.currentThread().interrupt(); } } /* (non-Javadoc) * @see org.opennms.netmgt.scheduler.Scheduler#schedule(long, org.opennms.netmgt.scheduler.ReadyRunnable) */ /** {@inheritDoc} */ public synchronized void schedule(long interval, final ReadyRunnable runnable) { final long timeToRun = getCurrentTime()+interval; ReadyRunnable timeKeeper = new ReadyRunnable() { public boolean isReady() { return getCurrentTime() >= timeToRun && runnable.isReady(); } public void run() { runnable.run(); } public String toString() { return runnable.toString()+" (ready in "+Math.max(0, timeToRun-getCurrentTime())+"ms)"; } }; schedule(timeKeeper, interval); } /* (non-Javadoc) * @see org.opennms.netmgt.scheduler.Scheduler#getCurrentTime() */ /** * <p>getCurrentTime</p> * * @return a long. */ public long getCurrentTime() { return System.currentTimeMillis(); } /* (non-Javadoc) * @see org.opennms.netmgt.scheduler.Scheduler#start() */ /** * <p>start</p> */ public synchronized void start() { Assert.state(m_worker == null, "The fiber has already run or is running"); m_worker = new Thread(this, getName()); m_worker.start(); m_status = STARTING; log().info("start: scheduler started"); } /* (non-Javadoc) * @see org.opennms.netmgt.scheduler.Scheduler#stop() */ /** * <p>stop</p> */ public synchronized void stop() { Assert.state(m_worker != null, "The fiber has never been started"); m_status = STOP_PENDING; m_worker.interrupt(); m_runner.shutdown(); log().info("stop: scheduler stopped"); } /* (non-Javadoc) * @see org.opennms.netmgt.scheduler.Scheduler#pause() */ /** * <p>pause</p> */ public synchronized void pause() { Assert.state(m_worker != null, "The fiber has never been started"); Assert.state(m_status != STOPPED && m_status != STOP_PENDING, "The fiber is not running or a stop is pending"); if (m_status == PAUSED) { return; } m_status = PAUSE_PENDING; notifyAll(); } /* (non-Javadoc) * @see org.opennms.netmgt.scheduler.Scheduler#resume() */ /** * <p>resume</p> */ public synchronized void resume() { Assert.state(m_worker != null, "The fiber has never been started"); Assert.state(m_status != STOPPED && m_status != STOP_PENDING, "The fiber is not running or a stop is pending"); if (m_status == RUNNING) { return; } m_status = RESUME_PENDING; notifyAll(); } /* (non-Javadoc) * @see org.opennms.netmgt.scheduler.Scheduler#getStatus() */ /** * <p>getStatus</p> * * @return a int. */ public synchronized int getStatus() { if (m_worker != null && m_worker.isAlive() == false) { m_status = STOPPED; } return m_status; } /** * Returns the name of this fiber. * * @return a {@link java.lang.String} object. */ public String getName() { return m_runner.toString(); } /** * Returns total number of elements currently scheduled. * * @return the sum of all the elements in the various queues */ public int getScheduled() { return m_scheduled; } /** * Returns the pool of threads that are used to executed the runnable * instances scheduled by the class' instance. * * @return thread pool */ public ExecutorService getRunner() { return m_runner; } /** * The main method of the scheduler. This method is responsible for checking * the runnable queues for ready objects and then enqueuing them into the * thread pool for execution. */ public void run() { synchronized (this) { m_status = RUNNING; } log().debug("run: scheduler running"); /* * Loop until a fatal exception occurs or until * the thread is interrupted. */ for (;;) { /* * Block if there is nothing in the queue(s). * When something is added to the queue it * signals us to wakeup. */ synchronized (this) { if (m_status != RUNNING && m_status != PAUSED && m_status != PAUSE_PENDING && m_status != RESUME_PENDING) { if (log().isDebugEnabled()) { log().debug("run: status = " + m_status + ", time to exit"); } break; } // if paused or pause pending then block while (m_status == PAUSE_PENDING || m_status == PAUSED) { if (m_status == PAUSE_PENDING) { log().debug("run: pausing."); } m_status = PAUSED; try { wait(); } catch (InterruptedException ex) { // exit break; } } // if resume pending then change to running if (m_status == RESUME_PENDING) { log().debug("run: resuming."); m_status = RUNNING; } if (m_scheduled == 0) { try { log().debug("run: no ready runnables scheduled, waiting..."); wait(); } catch (InterruptedException ex) { break; } } } /* * Cycle through the queues checking for * what's ready to run. The queues are keyed * by the interval, but the mapped elements * are peekable fifo queues. */ int runned = 0; synchronized (m_queues) { /* * Get an iterator so that we can cycle * through the queue elements. */ for (Entry<Long, PeekableFifoQueue<ReadyRunnable>> entry : m_queues.entrySet()) { /* * Peak for Runnable objects until * there are no more ready runnables. * * Also, only go through each queue once! * if we didn't add a count then it would * be possible to starve other queues. */ PeekableFifoQueue<ReadyRunnable> in = entry.getValue(); ReadyRunnable readyRun = null; int maxLoops = in.size(); do { try { readyRun = in.peek(); if (readyRun != null && readyRun.isReady()) { if (log().isDebugEnabled()) { log().debug("run: found ready runnable "+readyRun); } /* * Pop the interface/readyRunnable from the * queue for execution. */ in.remove(); // Add runnable to the execution queue m_runner.execute(readyRun); ++runned; } } catch (InterruptedException e) { return; // jump all the way out } catch (RejectedExecutionException e) { throw new UndeclaredThrowableException(e); } } while (readyRun != null && readyRun.isReady() && --maxLoops > 0); } } /* * Wait for 1 second if there were no runnables * executed during this loop, otherwise just * start over. */ synchronized (this) { m_scheduled -= runned; if (runned == 0) { try { wait(1000); } catch (InterruptedException ex) { break; // exit for loop } } } } log().debug("run: scheduler exiting, state = STOPPED"); synchronized (this) { m_status = STOPPED; } } private ThreadCategory log() { return ThreadCategory.getInstance(getClass()); } }