/*
* File : ThreadPool.java
* Created : 21-Nov-2003
* By : parg
*
* Azureus - a Java Bittorrent client
*
* This program 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 2 of the License.
*
* This program 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 ( see the LICENSE file ).
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package org.gudy.azureus2.core3.util;
/**
* @author parg
*
*/
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.gudy.azureus2.core3.config.COConfigurationManager;
import org.gudy.azureus2.core3.config.ParameterListener;
public class
ThreadPool
{
private static final boolean NAME_THREADS = Constants.IS_CVS_VERSION && System.getProperty( "az.thread.pool.naming.enable", "true" ).equals( "true" );
private static final boolean LOG_WARNINGS = false;
private static final int WARN_TIME = 10000;
private static List busy_pools = new ArrayList();
private static boolean busy_pool_timer_set = false;
private static boolean debug_thread_pool;
private static boolean debug_thread_pool_log_on;
static{
if ( System.getProperty("transitory.startup", "0").equals("0")){
AEDiagnostics.addEvidenceGenerator(
new AEDiagnosticsEvidenceGenerator()
{
public void
generate(
IndentWriter writer )
{
writer.println( "Thread Pools" );
try{
writer.indent();
List pools;
synchronized( busy_pools ){
pools = new ArrayList( busy_pools );
}
for (int i=0;i<pools.size();i++){
((ThreadPool)pools.get(i)).generateEvidence( writer );
}
}finally{
writer.exdent();
}
}
});
}
}
private static ThreadLocal tls =
new ThreadLocal()
{
public Object
initialValue()
{
return( null );
}
};
protected static void
checkAllTimeouts()
{
List pools;
// copy the busy pools to avoid potential deadlock due to synchronization
// nestings
synchronized( busy_pools ){
pools = new ArrayList( busy_pools );
}
for (int i=0;i<pools.size();i++){
((ThreadPool)pools.get(i)).checkTimeouts();
}
}
private String name;
private int max_size;
private int thread_name_index = 1;
private long execution_limit;
private List busy;
private boolean queue_when_full;
private List task_queue = new ArrayList();
private AESemaphore thread_sem;
private int reserved_target;
private int reserved_actual;
private int thread_priority = Thread.NORM_PRIORITY;
private boolean warn_when_full;
private long task_total;
private long task_total_last;
private Average task_average = Average.getInstance( WARN_TIME, 120 );
private boolean log_cpu = false || AEThread2.TRACE_TIMES;
public
ThreadPool(
String _name,
int _max_size )
{
this( _name, _max_size, false );
}
public
ThreadPool(
String _name,
int _max_size,
boolean _queue_when_full )
{
name = _name;
max_size = _max_size;
queue_when_full = _queue_when_full;
thread_sem = new AESemaphore( "ThreadPool::" + name, _max_size );
busy = new ArrayList( _max_size );
}
private void
generateEvidence(
IndentWriter writer )
{
writer.println( name + ": max=" + max_size +",qwf=" + queue_when_full + ",queue=" + task_queue.size() + ",busy=" + busy.size() + ",total=" + task_total + ":" + DisplayFormatters.formatDecimal(task_average.getDoubleAverage(),2) + "/sec");
}
public void
setWarnWhenFull()
{
warn_when_full = true;
}
public void
setLogCPU()
{
log_cpu = true;
}
public int
getMaxThreads()
{
return( max_size );
}
public void
setThreadPriority(
int _priority )
{
thread_priority = _priority;
}
public void
setExecutionLimit(
long millis )
{
synchronized( this ){
execution_limit = millis;
}
}
public threadPoolWorker run(AERunnable runnable) {
return( run(runnable, false, false));
}
/**
*
* @param runnable
* @param high_priority
* inserts at front if tasks queueing
*/
public threadPoolWorker run(AERunnable runnable, boolean high_priority, boolean manualRelease) {
if(manualRelease && !(runnable instanceof ThreadPoolTask))
throw new IllegalArgumentException("manual release only allowed for ThreadPoolTasks");
else if(manualRelease)
((ThreadPoolTask)runnable).setManualRelease();
// System.out.println( "Thread pool:" + name + " - sem = " + thread_sem.getValue() + ", queue = " + task_queue.size());
// not queueing, grab synchronous sem here
if ( !queue_when_full ){
if ( !thread_sem.reserveIfAvailable()){
// defend against recursive entry when in queuing mode (yes, it happens)
threadPoolWorker recursive_worker = (threadPoolWorker)tls.get();
if ( recursive_worker == null || recursive_worker.getOwner() != this ){
// do a blocking reserve here, not recursive
checkWarning();
thread_sem.reserve();
}else{
// run immediately
if ( runnable instanceof ThreadPoolTask ){
ThreadPoolTask task = (ThreadPoolTask)runnable;
task.worker = recursive_worker;
try{
task.taskStarted();
runIt( runnable );
task.join();
}finally{
task.taskCompleted();
}
}else{
runIt( runnable );
}
return( recursive_worker );
}
}
}
threadPoolWorker allocated_worker;
synchronized( this ){
if ( high_priority )
task_queue.add( 0, runnable );
else
task_queue.add( runnable );
// reserve if available is non-blocking
if ( queue_when_full && !thread_sem.reserveIfAvailable()){
allocated_worker = null;
checkWarning();
}else{
allocated_worker = new threadPoolWorker();
}
}
return( allocated_worker );
}
protected void
runIt(
AERunnable runnable )
{
if ( log_cpu ){
long start_cpu = log_cpu?AEJavaManagement.getThreadCPUTime():0;
long start_time = SystemTime.getHighPrecisionCounter();
runnable.run();
if ( start_cpu > 0 ){
long end_cpu = log_cpu?AEJavaManagement.getThreadCPUTime():0;
long diff_cpu = ( end_cpu - start_cpu ) / 1000000;
long end_time = SystemTime.getHighPrecisionCounter();
long diff_millis = ( end_time - start_time ) / 1000000;
if ( diff_cpu > 10 || diff_millis > 10){
System.out.println( TimeFormatter.milliStamp() + ": Thread: " + Thread.currentThread().getName() + ": " + runnable + " -> " + diff_cpu + "/" + diff_millis );
}
}
}else{
runnable.run();
}
}
protected void checkWarning() {
if (warn_when_full)
{
String task_names = "";
try
{
synchronized (ThreadPool.this)
{
for (int i = 0; i < busy.size(); i++)
{
threadPoolWorker x = (threadPoolWorker) busy.get(i);
AERunnable r = x.runnable;
if (r != null)
{
String name;
if (r instanceof ThreadPoolTask)
name = ((ThreadPoolTask) r).getName();
else
name = r.getClass().getName();
task_names += (task_names.length() == 0 ? "" : ",") + name;
}
}
}
} catch (Throwable e)
{}
Debug.out("Thread pool '" + getName() + "' is full (busy=" + task_names + ")");
warn_when_full = false;
}
}
public AERunnable[] getQueuedTasks() {
synchronized (this)
{
AERunnable[] res = new AERunnable[task_queue.size()];
task_queue.toArray(res);
return (res);
}
}
public int getQueueSize() {
synchronized (this)
{
return task_queue.size();
}
}
public boolean isQueued(AERunnable task) {
synchronized (this)
{
return task_queue.contains(task);
}
}
public AERunnable[]
getRunningTasks()
{
List runnables = new ArrayList();
synchronized( this ){
Iterator it = busy.iterator();
while( it.hasNext()){
threadPoolWorker worker = (threadPoolWorker)it.next();
AERunnable runnable = worker.getRunnable();
if ( runnable != null ){
runnables.add( runnable );
}
}
}
AERunnable[] res = new AERunnable[runnables.size()];
runnables.toArray(res);
return( res );
}
public int
getRunningCount()
{
int res = 0;
synchronized( this ){
Iterator it = busy.iterator();
while( it.hasNext()){
threadPoolWorker worker = (threadPoolWorker)it.next();
AERunnable runnable = worker.getRunnable();
if ( runnable != null ){
res++;
}
}
}
return( res );
}
public boolean
isFull()
{
return( thread_sem.getValue() == 0 );
}
public void
setMaxThreads(
int max )
{
if ( max > max_size ){
Debug.out( "should support this sometime..." );
return;
}
setReservedThreadCount( max_size - max );
}
public void
setReservedThreadCount(
int res )
{
synchronized( this ){
if ( res < 0 ){
res = 0;
}else if ( res > max_size ){
res = max_size;
}
int diff = res - reserved_actual;
while( diff < 0 ){
thread_sem.release();
reserved_actual--;
diff++;
}
while( diff > 0 ){
if ( thread_sem.reserveIfAvailable()){
reserved_actual++;
diff--;
}else{
break;
}
}
reserved_target = res;
}
}
protected void
checkTimeouts()
{
synchronized( this ){
long diff = task_total - task_total_last;
task_average.addValue( diff );
task_total_last = task_total;
if ( debug_thread_pool_log_on ){
System.out.println( "ThreadPool '" + getName() + "'/" + thread_name_index + ": max=" + max_size + ",sem=[" + thread_sem.getString() + "],busy=" + busy.size() + ",queue=" + task_queue.size());
}
long now = SystemTime.getMonotonousTime();
for (int i=0;i<busy.size();i++){
threadPoolWorker x = (threadPoolWorker)busy.get(i);
long elapsed = now - x.run_start_time;
if ( elapsed > ( (long)WARN_TIME * (x.warn_count+1))){
x.warn_count++;
if ( LOG_WARNINGS ){
DebugLight.out( x.getWorkerName() + ": running, elapsed = " + elapsed + ", state = " + x.state );
}
if ( execution_limit > 0 && elapsed > execution_limit ){
if ( LOG_WARNINGS ){
DebugLight.out( x.getWorkerName() + ": interrupting" );
}
AERunnable r = x.runnable;
if ( r != null ){
try{
if ( r instanceof ThreadPoolTask ){
((ThreadPoolTask)r).interruptTask();
}else{
x.interrupt();
}
}catch( Throwable e ){
DebugLight.printStackTrace( e );
}
}
}
}
}
}
}
public String getName() {
return (name);
}
void releaseManual(ThreadPoolTask toRelease) {
if( !toRelease.canManualRelease()){
throw new IllegalStateException("task not manually releasable");
}
synchronized( this ){
long elapsed = SystemTime.getMonotonousTime() - toRelease.worker.run_start_time;
if (elapsed > WARN_TIME && LOG_WARNINGS)
DebugLight.out(toRelease.worker.getWorkerName() + ": terminated, elapsed = " + elapsed + ", state = " + toRelease.worker.state);
if ( !busy.remove(toRelease.worker)){
throw new IllegalStateException("task already released");
}
// if debug is on we leave the pool registered so that we
// can trace on the timeout events
if (busy.size() == 0 && !debug_thread_pool){
synchronized (busy_pools){
busy_pools.remove(this);
}
}
if ( busy.size() == 0){
if ( reserved_target > reserved_actual ){
reserved_actual++;
}else{
thread_sem.release();
}
}else{
new threadPoolWorker();
}
}
}
public void registerThreadAsChild(threadPoolWorker parent)
{
if(tls.get() == null || tls.get() == parent)
tls.set(parent);
else
throw new IllegalStateException("another parent is already set for this thread");
}
public void deregisterThreadAsChild(threadPoolWorker parent)
{
if(tls.get() == parent)
tls.set(null);
else
throw new IllegalStateException("tls is not set to parent");
}
class threadPoolWorker extends AEThread2 {
private final String worker_name;
private volatile AERunnable runnable;
private long run_start_time;
private int warn_count;
private String state = "<none>";
protected threadPoolWorker()
{
super(NAME_THREADS?(name + " " + (thread_name_index)):name,true);
thread_name_index++;
setPriority(thread_priority);
worker_name = this.getName();
start();
}
public void run() {
tls.set(threadPoolWorker.this);
boolean autoRelease = true;
try
{
do
{
try
{
synchronized (ThreadPool.this)
{
if (task_queue.size() > 0)
runnable = (AERunnable) task_queue.remove(0);
else
break;
}
synchronized (ThreadPool.this)
{
run_start_time = SystemTime.getMonotonousTime();
warn_count = 0;
busy.add(threadPoolWorker.this);
task_total++;
if (busy.size() == 1)
{
synchronized (busy_pools)
{
if (!busy_pools.contains(ThreadPool.this))
{
busy_pools.add(ThreadPool.this);
if (!busy_pool_timer_set)
{
// we have to defer this action rather
// than running as a static initialiser
// due to the dependency between
// ThreadPool, Timer and ThreadPool again
COConfigurationManager.addAndFireParameterListeners(new String[] { "debug.threadpool.log.enable", "debug.threadpool.debug.trace" }, new ParameterListener()
{
public void parameterChanged(String name) {
debug_thread_pool = COConfigurationManager.getBooleanParameter("debug.threadpool.log.enable", false);
debug_thread_pool_log_on = COConfigurationManager.getBooleanParameter("debug.threadpool.debug.trace", false);
}
});
busy_pool_timer_set = true;
SimpleTimer.addPeriodicEvent("ThreadPool:timeout", WARN_TIME, new TimerEventPerformer()
{
public void perform(TimerEvent event) {
checkAllTimeouts();
}
});
}
}
}
}
}
if (runnable instanceof ThreadPoolTask)
{
ThreadPoolTask tpt = (ThreadPoolTask) runnable;
tpt.worker = this;
String task_name = NAME_THREADS?tpt.getName():null;
try
{
if (task_name != null)
setName(worker_name + "{" + task_name + "}");
tpt.taskStarted();
runIt(runnable);
} finally
{
if (task_name != null)
setName(worker_name);
if(tpt.isAutoReleaseAndAllowManual())
tpt.taskCompleted();
else
{
autoRelease = false;
break;
}
}
} else
runIt(runnable);
} catch (Throwable e)
{
DebugLight.printStackTrace(e);
} finally
{
if(autoRelease)
{
synchronized (ThreadPool.this)
{
long elapsed = SystemTime.getMonotonousTime() - run_start_time;
if (elapsed > WARN_TIME && LOG_WARNINGS)
DebugLight.out(getWorkerName() + ": terminated, elapsed = " + elapsed + ", state = " + state);
busy.remove(threadPoolWorker.this);
// if debug is on we leave the pool registered so that we
// can trace on the timeout events
if (busy.size() == 0 && !debug_thread_pool)
synchronized (busy_pools)
{
busy_pools.remove(ThreadPool.this);
}
}
}
}
} while (runnable != null);
} catch (Throwable e)
{
DebugLight.printStackTrace(e);
} finally
{
if ( autoRelease){
synchronized (ThreadPool.this){
if ( reserved_target > reserved_actual ){
reserved_actual++;
}else{
thread_sem.release();
}
}
}
tls.set(null);
}
}
public void setState(String _state) {
//System.out.println( "state = " + _state );
state = _state;
}
public String getState() {
return (state);
}
protected String getWorkerName() {
return (worker_name);
}
protected ThreadPool getOwner() {
return (ThreadPool.this);
}
protected AERunnable getRunnable() {
return (runnable);
}
}
}