/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.catalina.core;
import java.util.Collection;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.catalina.Executor;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.LifecycleListener;
import org.apache.catalina.util.LifecycleSupport;
import java.util.concurrent.RejectedExecutionException;
public class StandardThreadExecutor implements Executor {
// ---------------------------------------------- Properties
/**
* Default thread priority
*/
protected int threadPriority = Thread.NORM_PRIORITY;
/**
* Run threads in daemon or non-daemon state
*/
protected boolean daemon = true;
/**
* Default name prefix for the thread name
*/
protected String namePrefix = "tomcat-exec-";
/**
* max number of threads
*/
protected int maxThreads = 200;
/**
* min number of threads
*/
protected int minSpareThreads = 25;
/**
* idle time in milliseconds
*/
protected int maxIdleTime = 60000;
/**
* The executor we use for this component
*/
protected ThreadPoolExecutor executor = null;
/**
* the name of this thread pool
*/
protected String name;
/**
* Number of tasks submitted and not yet completed.
*/
protected AtomicInteger submittedTasksCount;
/**
* The maximum number of elements that can queue up before we reject them
*/
protected int maxQueueSize = Integer.MAX_VALUE;
private LifecycleSupport lifecycle = new LifecycleSupport(this);
// ---------------------------------------------- Constructors
public StandardThreadExecutor() {
//empty constructor for the digester
}
// ---------------------------------------------- Public Methods
public void start() throws LifecycleException {
lifecycle.fireLifecycleEvent(BEFORE_START_EVENT, null);
TaskQueue taskqueue = new TaskQueue(maxQueueSize);
TaskThreadFactory tf = new TaskThreadFactory(namePrefix);
lifecycle.fireLifecycleEvent(START_EVENT, null);
executor = new ThreadPoolExecutor(getMinSpareThreads(), getMaxThreads(), maxIdleTime, TimeUnit.MILLISECONDS,taskqueue, tf) {
@Override
protected void afterExecute(Runnable r, Throwable t) {
AtomicInteger atomic = submittedTasksCount;
if(atomic!=null) {
atomic.decrementAndGet();
}
}
};
taskqueue.setParent( (ThreadPoolExecutor) executor);
submittedTasksCount = new AtomicInteger();
lifecycle.fireLifecycleEvent(AFTER_START_EVENT, null);
}
public void stop() throws LifecycleException{
lifecycle.fireLifecycleEvent(BEFORE_STOP_EVENT, null);
lifecycle.fireLifecycleEvent(STOP_EVENT, null);
if ( executor != null ) executor.shutdown();
executor = null;
submittedTasksCount = null;
lifecycle.fireLifecycleEvent(AFTER_STOP_EVENT, null);
}
// This method is not used by Tomcat 6, but is available in later versions
public void execute(Runnable command, long timeout, TimeUnit unit) {
if ( executor != null ) {
submittedTasksCount.incrementAndGet();
try {
executor.execute(command);
} catch (RejectedExecutionException rx) {
//there could have been contention around the queue
try {
if ( !( (TaskQueue) executor.getQueue()).force(command,timeout,unit) ) {
submittedTasksCount.decrementAndGet();
throw new RejectedExecutionException("Work queue full.");
}
}catch (InterruptedException x) {
throw new RejectedExecutionException("Interrupted.",x);
}
}
} else throw new IllegalStateException("StandardThreadPool not started.");
}
public void execute(Runnable command) {
if ( executor != null ) {
submittedTasksCount.incrementAndGet();
try {
executor.execute(command);
} catch (RejectedExecutionException rx) {
//there could have been contention around the queue
if ( !( (TaskQueue) executor.getQueue()).force(command) ) {
submittedTasksCount.decrementAndGet();
throw new RejectedExecutionException("Work queue full.");
}
}
} else throw new IllegalStateException("StandardThreadPool not started.");
}
public int getThreadPriority() {
return threadPriority;
}
public boolean isDaemon() {
return daemon;
}
public String getNamePrefix() {
return namePrefix;
}
public int getMaxIdleTime() {
return maxIdleTime;
}
public int getMaxThreads() {
return maxThreads;
}
public int getMinSpareThreads() {
return minSpareThreads;
}
public String getName() {
return name;
}
public void setThreadPriority(int threadPriority) {
this.threadPriority = threadPriority;
}
public void setDaemon(boolean daemon) {
this.daemon = daemon;
}
public void setNamePrefix(String namePrefix) {
this.namePrefix = namePrefix;
}
public void setMaxIdleTime(int maxIdleTime) {
this.maxIdleTime = maxIdleTime;
if (executor != null) {
executor.setKeepAliveTime(maxIdleTime, TimeUnit.MILLISECONDS);
}
}
public void setMaxThreads(int maxThreads) {
this.maxThreads = maxThreads;
if (executor != null) {
executor.setMaximumPoolSize(maxThreads);
}
}
public void setMinSpareThreads(int minSpareThreads) {
this.minSpareThreads = minSpareThreads;
if (executor != null) {
executor.setCorePoolSize(minSpareThreads);
}
}
public void setName(String name) {
this.name = name;
}
public void setMaxQueueSize(int size) {
this.maxQueueSize = size;
}
public int getMaxQueueSize() {
return maxQueueSize;
}
/**
* Add a LifecycleEvent listener to this component.
*
* @param listener The listener to add
*/
public void addLifecycleListener(LifecycleListener listener) {
lifecycle.addLifecycleListener(listener);
}
/**
* Get the lifecycle listeners associated with this lifecycle. If this
* Lifecycle has no listeners registered, a zero-length array is returned.
*/
public LifecycleListener[] findLifecycleListeners() {
return lifecycle.findLifecycleListeners();
}
/**
* Remove a LifecycleEvent listener from this component.
*
* @param listener The listener to remove
*/
public void removeLifecycleListener(LifecycleListener listener) {
lifecycle.removeLifecycleListener(listener);
}
// Statistics from the thread pool
public int getActiveCount() {
return (executor != null) ? executor.getActiveCount() : 0;
}
public long getCompletedTaskCount() {
return (executor != null) ? executor.getCompletedTaskCount() : 0;
}
public int getCorePoolSize() {
return (executor != null) ? executor.getCorePoolSize() : 0;
}
public int getLargestPoolSize() {
return (executor != null) ? executor.getLargestPoolSize() : 0;
}
public int getPoolSize() {
return (executor != null) ? executor.getPoolSize() : 0;
}
public int getQueueSize() {
return (executor != null) ? executor.getQueue().size() : -1;
}
// ---------------------------------------------- TaskQueue Inner Class
class TaskQueue extends LinkedBlockingQueue<Runnable> {
ThreadPoolExecutor parent = null;
public TaskQueue() {
super();
}
public TaskQueue(int capacity) {
super(capacity);
}
public TaskQueue(Collection<? extends Runnable> c) {
super(c);
}
public void setParent(ThreadPoolExecutor tp) {
parent = tp;
}
public boolean force(Runnable o) {
if ( parent.isShutdown() ) throw new RejectedExecutionException("Executor not running, can't force a command into the queue");
return super.offer(o); //forces the item onto the queue, to be used if the task is rejected
}
public boolean force(Runnable o, long timeout, TimeUnit unit) throws InterruptedException {
if ( parent.isShutdown() ) throw new RejectedExecutionException("Executor not running, can't force a command into the queue");
return super.offer(o,timeout,unit); //forces the item onto the queue, to be used if the task is rejected
}
public boolean offer(Runnable o) {
//we can't do any checks
if (parent==null) return super.offer(o);
int poolSize = parent.getPoolSize();
//we are maxed out on threads, simply queue the object
if (parent.getPoolSize() == parent.getMaximumPoolSize()) return super.offer(o);
//we have idle threads, just add it to the queue
//note that we don't use getActiveCount(), see BZ 49730
AtomicInteger submittedTasksCount = StandardThreadExecutor.this.submittedTasksCount;
if(submittedTasksCount!=null) {
if (submittedTasksCount.get()<=poolSize) return super.offer(o);
}
//if we have less threads than maximum force creation of a new thread
if (poolSize<parent.getMaximumPoolSize()) return false;
//if we reached here, we need to add it to the queue
return super.offer(o);
}
}
// ---------------------------------------------- ThreadFactory Inner Class
class TaskThreadFactory implements ThreadFactory {
final ThreadGroup group;
final AtomicInteger threadNumber = new AtomicInteger(1);
final String namePrefix;
TaskThreadFactory(String namePrefix) {
SecurityManager s = System.getSecurityManager();
group = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup();
this.namePrefix = namePrefix;
}
public Thread newThread(Runnable r) {
Thread t = new Thread(group, r, namePrefix + threadNumber.getAndIncrement());
t.setDaemon(daemon);
t.setPriority(getThreadPriority());
return t;
}
}
}