org.opencms.scheduler.CmsSchedulerThreadPool Maven / Gradle / Ivy
Show all versions of opencms-core Show documentation
/*
* This library is part of OpenCms -
* the Open Source Content Management System
*
* Copyright (c) Alkacon Software GmbH & Co. KG (http://www.alkacon.com)
*
* This library 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 library 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.
*
* For further information about Alkacon Software GmbH & Co. KG, please see the
* company website: http://www.alkacon.com
*
* For further information about OpenCms, please see the
* project website: http://www.opencms.org
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*
* This library is based to some extend on code from the
* OpenSymphony Quartz project. Original copyright notice:
*
* Copyright James House (c) 2001-2005
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met: 1.
* Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer. 2. Redistributions in
* binary form must reproduce the above copyright notice, this list of
* conditions and the following disclaimer in the documentation and/or other
* materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.opencms.scheduler;
import org.opencms.main.CmsLog;
import org.apache.commons.logging.Log;
import org.quartz.SchedulerConfigException;
import org.quartz.spi.ThreadPool;
/**
* Simple thread pool used for the Quartz scheduler in OpenCms.
*
* @since 6.0.0
*/
public class CmsSchedulerThreadPool implements ThreadPool {
/** The log object for this class. */
private static final Log LOG = CmsLog.getLog(CmsSchedulerThreadPool.class);
/** The current thread count. */
private int m_currentThreadCount;
/** The inheri group. */
private boolean m_inheritGroup;
/** The inherit loader. */
private boolean m_inheritLoader;
/** The initial thread count. */
private int m_initialThreadCount;
/** Flag indicating if the system is shutting down. */
private boolean m_isShutdown;
/** Flag indicating whether to create thread deamons. */
private boolean m_makeThreadsDaemons;
/** The max thread count. */
private int m_maxThreadCount;
/** The next runnable. */
private Runnable m_nextRunnable;
/** The next runnable lock. */
private Object m_nextRunnableLock;
/** The thread group. */
private ThreadGroup m_threadGroup;
/** The thread name prefix. */
private String m_threadNamePrefix;
/** The thread priority. */
private int m_threadPriority;
/** The threads. */
private CmsSchedulerThread[] m_workers;
/**
* Create a new CmsSchedulerThreadPool
with default values.
*
* This will create a pool with 0 initial and 10 maximum threads running
* in normal priority.
*
* @see #CmsSchedulerThreadPool(int, int, int)
*/
public CmsSchedulerThreadPool() {
this(0, 10, Thread.NORM_PRIORITY);
}
/**
* Create a new CmsSchedulerThreadPool
with the specified number
* of threads that have the given priority.
*
* The OpenCms scheduler thread pool will initially start with provided number of
* active scheduler threads.
* When a thread is requested by the scheduler, and no "free" threads are available,
* a new thread will be added to the pool and used for execution. The pool
* will be allowed to grow until it has reached the configured number
* of maximum threads.
*
* @param initialThreadCount the initial number of threads for the pool
* @param maxThreadCount maximum number of threads the pool is allowed to grow
* @param threadPriority the thread priority for the scheduler threads
*
* @see java.lang.Thread
*/
public CmsSchedulerThreadPool(int initialThreadCount, int maxThreadCount, int threadPriority) {
m_inheritGroup = true;
m_inheritLoader = true;
m_nextRunnableLock = new Object();
m_threadNamePrefix = "OpenCms: Scheduler Thread ";
m_makeThreadsDaemons = true;
m_initialThreadCount = initialThreadCount;
m_currentThreadCount = 0;
m_maxThreadCount = maxThreadCount;
m_threadPriority = threadPriority;
}
/**
* @see org.quartz.spi.ThreadPool
*
* @return if the pool should be blocked for available threads
*/
public int blockForAvailableThreads() {
// Waiting will be done in runInThread so we always return 1
return 1;
}
/**
* @see org.quartz.spi.ThreadPool#getPoolSize()
*/
public int getPoolSize() {
return m_currentThreadCount;
}
/**
* Returns the thread priority of the threads in the scheduler pool.
*
* @return the thread priority of the threads in the scheduler pool
*/
public int getThreadPriority() {
return m_threadPriority;
}
/**
* @see org.quartz.spi.ThreadPool#initialize()
*/
public void initialize() throws SchedulerConfigException {
if ((m_maxThreadCount <= 0) || (m_maxThreadCount > 200)) {
throw new SchedulerConfigException(Messages.get().getBundle().key(Messages.ERR_MAX_THREAD_COUNT_BOUNDS_0));
}
if ((m_initialThreadCount < 0) || (m_initialThreadCount > m_maxThreadCount)) {
throw new SchedulerConfigException(Messages.get().getBundle().key(Messages.ERR_INIT_THREAD_COUNT_BOUNDS_0));
}
if ((m_threadPriority <= 0) || (m_threadPriority > 9)) {
throw new SchedulerConfigException(
Messages.get().getBundle().key(Messages.ERR_SCHEDULER_PRIORITY_BOUNDS_0));
}
if (m_inheritGroup) {
m_threadGroup = Thread.currentThread().getThreadGroup();
} else {
// follow the threadGroup tree to the root thread group
m_threadGroup = Thread.currentThread().getThreadGroup();
ThreadGroup parent = m_threadGroup;
while (!parent.getName().equals("main")) {
m_threadGroup = parent;
parent = m_threadGroup.getParent();
}
m_threadGroup = new ThreadGroup(parent, this.getClass().getName());
}
if (m_inheritLoader) {
LOG.debug(
Messages.get().getBundle().key(
Messages.LOG_USING_THREAD_CLASSLOADER_1,
Thread.currentThread().getName()));
}
// create the worker threads and start them
m_workers = new CmsSchedulerThread[m_maxThreadCount];
for (int i = 0; i < m_initialThreadCount; ++i) {
growThreadPool();
}
}
/**
* Run the given Runnable
object in the next available
* Thread
.
*
* If while waiting the thread pool is asked to
* shut down, the Runnable is executed immediately within a new additional
* thread.
*
* @param runnable the Runnable
to run
* @return true if the Runnable
was run
*/
public boolean runInThread(Runnable runnable) {
if (runnable == null) {
return false;
}
if (m_isShutdown) {
LOG.debug(Messages.get().getBundle().key(Messages.LOG_THREAD_POOL_UNAVAILABLE_0));
return false;
}
boolean hasNextRunnable;
synchronized (m_nextRunnableLock) {
// must synchronize here to avoid potential double checked locking
hasNextRunnable = (m_nextRunnable != null);
}
if (hasNextRunnable || (m_currentThreadCount == 0)) {
// try to grow the thread pool since other runnables are already waiting
growThreadPool();
}
synchronized (m_nextRunnableLock) {
// wait until a worker thread has taken the previous Runnable
// or until the thread pool is asked to shutdown
while ((m_nextRunnable != null) && !m_isShutdown) {
try {
m_nextRunnableLock.wait(1000);
} catch (InterruptedException e) {
// can be ignores
}
}
// during normal operation, not shutdown, set the nextRunnable
// and notify the worker threads waiting (getNextRunnable())
if (!m_isShutdown) {
m_nextRunnable = runnable;
m_nextRunnableLock.notifyAll();
}
}
// if the thread pool is going down, execute the Runnable
// within a new additional worker thread (no thread from the pool)
// note: the synchronized section should be as short (time) as
// possible as starting a new thread is not a quick action
if (m_isShutdown) {
CmsSchedulerThread thread = new CmsSchedulerThread(
this,
m_threadGroup,
m_threadNamePrefix + "(final)",
m_threadPriority,
false,
runnable);
thread.start();
}
return true;
}
/**
* Terminate any worker threads in this thread group.
*
* Jobs currently in progress will be allowed to complete.
*/
public void shutdown() {
shutdown(true);
}
/**
* Terminate all threads in this thread group.
*
* @param waitForJobsToComplete if true,, all current jobs will be allowed to complete
*/
public void shutdown(boolean waitForJobsToComplete) {
m_isShutdown = true;
// signal each scheduler thread to shut down
for (int i = 0; i < m_currentThreadCount; i++) {
if (m_workers[i] != null) {
m_workers[i].shutdown();
}
}
// give waiting (wait(1000)) worker threads a chance to shut down
// active worker threads will shut down after finishing their
// current job
synchronized (m_nextRunnableLock) {
m_nextRunnableLock.notifyAll();
}
if (waitForJobsToComplete) {
// wait until all worker threads are shut down
int alive = m_currentThreadCount;
while (alive > 0) {
alive = 0;
for (int i = 0; i < m_currentThreadCount; i++) {
if (m_workers[i].isAlive()) {
try {
if (LOG.isDebugEnabled()) {
LOG.debug(
Messages.get().getBundle().key(Messages.LOG_THREAD_POOL_WAITING_1, new Integer(i)));
}
// note: with waiting infinite - join(0) - the application
// may appear to 'hang'
// waiting for a finite time however requires an additional loop (alive)
alive++;
m_workers[i].join(200);
} catch (InterruptedException e) {
// can be ignored
}
}
}
}
int activeCount = m_threadGroup.activeCount();
if ((activeCount > 0) && LOG.isInfoEnabled()) {
LOG.info(
Messages.get().getBundle().key(Messages.LOG_THREAD_POOL_STILL_ACTIVE_1, new Integer(activeCount)));
}
if (LOG.isDebugEnabled()) {
LOG.debug(Messages.get().getBundle().key(Messages.LOG_THREAD_POOL_SHUTDOWN_0));
}
}
}
/**
* Dequeue the next pending Runnable
.
*
* @return the next pending Runnable
* @throws InterruptedException if something goes wrong
*/
protected Runnable getNextRunnable() throws InterruptedException {
Runnable toRun = null;
// Wait for new Runnable (see runInThread()) and notify runInThread()
// in case the next Runnable is already waiting.
synchronized (m_nextRunnableLock) {
if (m_nextRunnable == null) {
m_nextRunnableLock.wait(1000);
}
if (m_nextRunnable != null) {
toRun = m_nextRunnable;
m_nextRunnable = null;
m_nextRunnableLock.notifyAll();
}
}
return toRun;
}
/**
* Grows the thread pool by one new thread if the maximum pool size
* has not been reached.
*/
private void growThreadPool() {
if (m_currentThreadCount < m_maxThreadCount) {
// if maximum number is not reached grow the thread pool
synchronized (m_nextRunnableLock) {
m_workers[m_currentThreadCount] = new CmsSchedulerThread(
this,
m_threadGroup,
m_threadNamePrefix + m_currentThreadCount,
m_threadPriority,
m_makeThreadsDaemons);
m_workers[m_currentThreadCount].start();
if (m_inheritLoader) {
m_workers[m_currentThreadCount].setContextClassLoader(
Thread.currentThread().getContextClassLoader());
}
// increas the current size
m_currentThreadCount++;
// notify the waiting threads
m_nextRunnableLock.notifyAll();
}
}
}
}