/*
* Copyright 2000-2001,2004 The Apache Software Foundation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.jetspeed.services.threadpool;
// Java Stuff
import java.util.Vector;
import javax.servlet.ServletConfig;
import org.apache.jetspeed.services.logging.JetspeedLogFactoryService;
import org.apache.jetspeed.services.logging.JetspeedLogger;
import org.apache.turbine.services.TurbineBaseService;
import org.apache.turbine.services.TurbineServices;
import org.apache.turbine.services.resources.ResourceService;
/**
* This is a Service that provides a simple threadpool usable by all thread
* intensive classes in order to optimize resources utilization screen: <br>
*
* <p>
* It uses 3 parameters for contolling resource usage:
* <dl>
* <dt>init.count</dt>
* <dd>The number of threads to start at initizaliation</dd>
* <dt>max.count</dt>
* <dd>The maximum number of threads started by this service</dd>
* <dt>minspare.count</dt>
* <dd>The pool tries to keep lways this minimum number if threads available
* </dd>
* </dl>
* </p>
*
* @author <a href="mailto:burton@apache.org">Kevin A. Burton </a>
* @author <a href="mailto:raphael@apache.org">Rapha�l Luta </a>
* @author <a href="mailto:sgala@hisitech.com">Santiago Gala </a>
*/
public class JetspeedThreadPoolService extends TurbineBaseService implements
ThreadPoolService {
/**
* Static initialization of the logger for this class
*/
protected static final JetspeedLogger logger = JetspeedLogFactoryService
.getLogger(JetspeedThreadPoolService.class.getName());
/**
* The number of threads to create on initialization
*/
private int initThreads = 50;
/**
* The maximum number of threads that should ever be created.
*/
private int maxThreads = 100;
/**
* The minimum amount of threads that should always be available
*/
private int minSpareThreads = 15;
/**
* The default priority to use when creating new threads.
*/
public static final int DEFAULT_THREAD_PRIORITY = Thread.MIN_PRIORITY;
/**
* Stores threads that are available within the pool.
*/
private Vector availableThreads = new Vector();
/**
* The thread group used for all created threads.
*/
private ThreadGroup tg = new ThreadGroup("JetspeedThreadPoolService");
/**
* Create a new queue for adding Runnable objects to.
*/
private Queue queue = new Queue();
/**
* Holds the total number of threads that have ever been processed.
*/
private int count = 0;
public static final String SERVICE_NAME = "ThreadPool";
/**
* Constructor.
*
* @exception Exception,
* a generic exception.
*/
public JetspeedThreadPoolService() throws Exception {
}
/**
* Late init. Don't return control until early init says we're done.
*/
public void init() {
while (!getInit()) {
try {
Thread.sleep(500);
} catch (InterruptedException ie) {
logger.info("ThreadPool service: Waiting for init()...");
}
}
}
/**
* Called during Turbine.init()
*
* @param config
* A ServletConfig.
*/
public synchronized void init(ServletConfig config) {
if (getInit()) {
// Already inited
return;
}
try {
logger.info("JetspeedThreadPoolService early init()....starting!");
initThreadpool(config);
setInit(true);
logger.info("JetspeedThreadPoolService early init()....finished!");
} catch (Exception e) {
logger.error("Cannot initialize JetspeedThreadPoolService!", e);
}
// we don't call setInit(true) yet, because we want init() to be called also
}
/**
* Processes the Runnable object with an available thread at default priority
*
* @see #process( Runnable, int )
* @param runnable
* the runnable code to process
*/
public void process(Runnable runnable) {
process(runnable, Thread.MIN_PRIORITY);
}
/**
* Process a Runnable object by allocating a Thread for it at the given
* priority
*
* @param runnable
* the runnable code to process
* @param priority
* the priority used be the thread that will run this runnable
*/
public void process(Runnable runnable, int priority) {
RunnableThread thread = this.getAvailableThread();
if (thread == null) {
this.getQueue().add(runnable);
} else {
try {
synchronized (thread) {
// get the default priority of this Thread
int defaultPriority = thread.getPriority();
if (defaultPriority != priority) {
// setting priority triggers security checks,
// so we do it only if needed.
thread.setPriority(priority);
}
thread.setRunnable(runnable);
thread.notify();
}
} catch (Throwable t) {
logger.error("Throwable", t);
}
}
}
/**
* Get the number of threads that have been created
*
* @return the number of threads currently created by the pool
*/
public int getThreadCount() {
return this.tg.activeCount();
}
/**
* Get the number of threads that are available.
*
* @return the number of threads available in the pool
*/
public int getAvailableThreadCount() {
return this.availableThreads.size();
}
/**
* Get the current length of the Runnable queue, waiting for processing
*
* @return the length of the queue of waiting processes
*/
public int getQueueLength() {
return this.getQueue().size();
}
/**
* Get the number of threads that have successfully been processed for logging
* and debugging purposes.
*
* @return the number of processes executed since initialization
*/
public int getThreadProcessedCount() {
return this.count;
}
/**
* Get the queue used by the JetspeedThreadPoolService
*
* @return the queue holding the waiting processes
*/
Queue getQueue() {
return this.queue;
}
/**
* Place this thread back into the pool so that it can be used again
*
* @param thread
* the thread to release back to the pool
*/
void release(RunnableThread thread) {
synchronized (this.availableThreads) {
this.availableThreads.addElement(thread);
++this.count;
/*
* It is important to synchronize here because it is possible that between
* the time we check the queue and we get this another thread might return
* and fetch the queue to the end.
*/
synchronized (this.getQueue()) {
// now if there are any objects in the queue add one for processing to
// the thread that you just freed up.
if (this.getQueue().size() > 0) {
Runnable r = this.getQueue().get();
if (r != null) {
this.process(r);
} else {
logger.info("JetspeedThreadPoolService: no Runnable found.");
}
}
}
}
}
/**
* This method initialized the ThreadPool
*
* @param config
* A ServletConfig.
*/
private void initThreadpool(ServletConfig config) {
// Properties props = getProperties();
try {
// get configuration parameters from Jetspeed Resources
ResourceService serviceConf = ((TurbineServices) TurbineServices
.getInstance()).getResources(JetspeedThreadPoolService.SERVICE_NAME);
this.initThreads = serviceConf.getInt("init.count", 10);
this.maxThreads = serviceConf.getInt("max.count", 50);
this.minSpareThreads = serviceConf.getInt("minspare.count", 10);
// this.initThreads = Integer.parseInt( props.getProperty(
// "services.ThreadPool.init.count" ) );
// this.maxThreads = Integer.parseInt( props.getProperty(
// "services.ThreadPool.max.count" ) );
// this.minSpareThreads = Integer.parseInt( props.getProperty(
// "services.ThreadPool.minspare.count" ) );
} catch (NumberFormatException e) {
logger.error("Invalid number format in properties", e);
}
// create the number of threads needed for initialization
createThreads(this.initThreads);
}
/**
* Create "count" number of threads and make them available.
*
* @param count
* the number of threads to create
*/
private synchronized void createThreads(int count) {
// if the amount of threads you are about to create would end up being
// greater than maxThreads then just cap this off to the end point so that
// you end up with exactly maxThreads
if (this.getThreadCount() < this.maxThreads
&& this.getThreadCount() + count > this.maxThreads) {
count = this.maxThreads - this.getThreadCount();
} else if (this.getThreadCount() >= this.maxThreads) {
return;
}
logger.info("JetspeedThreadPoolService: creating " + count
+ " more thread(s) for a total of: " + (this.getThreadCount() + count));
for (int i = 0; i < count; ++i) {
// RunnableThread has a static numbering counter
RunnableThread thread = new RunnableThread(this.tg);
thread.setPriority(DEFAULT_THREAD_PRIORITY);
thread.start(); // The thread calls release to add...
// SGP this.availableThreads.addElement( thread );
}
}
/**
* Get a thread that is available from the pool or null if there are no more
* threads left.
*
* @return a thread from the pool or null if non available
*/
private RunnableThread getAvailableThread() {
synchronized (this.availableThreads) {
// if the current number of available threads is less than minSpareThreads
// then we need to create more
if (this.getAvailableThreadCount() < this.minSpareThreads) {
this.createThreads(this.minSpareThreads);
}
// now if there aren't any threads available then just return null.
if (this.getAvailableThreadCount() == 0) {
return null;
}
RunnableThread thread = null;
// get the element to use
int id = this.availableThreads.size() - 1;
thread = (RunnableThread) this.availableThreads.elementAt(id);
this.availableThreads.removeElementAt(id);
return thread;
}
}
}
/**
* Handles holding Runnables until they are ready to be processed. This is an
* impl of a FIFO (First In First Out) Queue. This makes it possible to add
* Runnable objects so that they get processed and they pass through the queue
* in a predictable fashion.
*
* @author <a href="mailto:burton@apache.org">Kevin A. Burton </a>
*/
class Queue {
/**
* Holds Runnables that have been requested to process but there are no
* threads available.
*/
private Vector queue = new Vector();
/**
* Add a Runnable object into the queue.
*
* @param runnable
* the process to add to the queue
*/
public synchronized void add(Runnable runnable) {
queue.insertElementAt(runnable, 0);
}
/**
* Get a Runnable object from the queue, and then remove it. Return null if no
* more Runnable objects exist.
*
* @return the first Runnable stored in the queue or null if empty
*/
public synchronized Runnable get() {
if (this.queue.size() == 0) {
JetspeedThreadPoolService.logger
.info("JetspeedThreadPoolService->Queue: No more Runnables left in queue. Returning null");
return null;
}
int id = queue.size() - 1;
Runnable runnable = (Runnable) queue.elementAt(id);
this.queue.removeElementAt(id);
return runnable;
}
/**
* Return the size of the queue.
*
* @return the size of the queue
*/
public int size() {
return this.queue.size();
}
}