/* This code is part of Freenet. It is distributed under the GNU General * Public License, version 2 (or at your option any later version). See * http://www.gnu.org/ for further details of the GPL. */ package freenet.support; import static java.util.concurrent.TimeUnit.MINUTES; import java.util.ArrayList; import java.util.concurrent.atomic.AtomicLong; import freenet.node.PrioRunnable; import freenet.support.Logger.LogLevel; import freenet.support.io.NativeThread; /** * Pooled Executor implementation. Create a thread when we need one, let them die * after 5 minutes of inactivity. * @author toad */ public class PooledExecutor implements Executor { /** All threads running or waiting */ private final int[] runningThreads = new int[NativeThread.JAVA_PRIORITY_RANGE + 1]; /** Threads waiting for a job */ @SuppressWarnings("unchecked") private final ArrayList<MyThread>[] waitingThreads = new ArrayList[runningThreads.length]; private volatile int waitingThreadsCount; AtomicLong[] threadCounter = new AtomicLong[runningThreads.length]; private long jobCount; private long jobMisses; private static boolean logMINOR; // Ticker thread that runs at maximum priority. private Ticker ticker; public synchronized void setTicker(Ticker ticker) { this.ticker = ticker; } public PooledExecutor() { for(int i = 0; i < runningThreads.length; i++) { /* runningThreads[i] = 0; */ waitingThreads[i] = new ArrayList<MyThread>(); threadCounter[i] = new AtomicLong(); } waitingThreadsCount = 0; } /** Maximum time a thread will wait for a job */ static final long TIMEOUT = MINUTES.toMillis(1); public void start() { logMINOR = Logger.shouldLog(LogLevel.MINOR, this); } @Override public void execute(Runnable job) { execute(job, "<noname>"); } @Override public void execute(Runnable job, String jobName) { execute(job, jobName, false); } @Override public void execute(Runnable runnable, String jobName, boolean fromTicker) { int prio = NativeThread.NORM_PRIORITY; if(runnable instanceof PrioRunnable) prio = ((PrioRunnable) runnable).getPriority(); if(logMINOR) Logger.minor(this, "Executing " + runnable + " as " + jobName + " at prio " + prio); if(prio < NativeThread.MIN_PRIORITY || prio > NativeThread.MAX_PRIORITY) throw new IllegalArgumentException("Unreconized priority level : " + prio + '!'); Job job = new Job(runnable, jobName); while(true) { MyThread t = null; boolean miss = false; synchronized(this) { jobCount++; if(!waitingThreads[prio - 1].isEmpty()) { t = waitingThreads[prio - 1].remove(waitingThreads[prio - 1].size() - 1); if (t != null) waitingThreadsCount--; if(logMINOR) Logger.minor(this, "Reusing thread " + t); } else { // Must create new thread if(ticker != null && (!fromTicker) && NativeThread.usingNativeCode() && prio > Thread.currentThread().getPriority()) { // Get the ticker to create a thread for it with the right priority, since we can't. ticker.queueTimedJob(runnable, jobName, 0, true, false); return; } miss = true; } } // miss if (miss) { long threadNo = threadCounter[prio - 1].getAndIncrement(); // Will be coalesced by thread count listings if we use "@" or "for" t = new MyThread("Pooled thread awaiting work @" + threadNo+" for prio "+prio, job, threadNo, prio, !fromTicker); t.setDaemon(true); synchronized(this) { runningThreads[prio - 1]++; jobMisses++; if(logMINOR) Logger.minor(this, "Jobs: " + jobMisses + " misses of " + jobCount + " starting urgently " + jobName); } t.start(); return; } // use existing thread synchronized(t) { if(!t.alive) continue; if(t.nextJob != null) continue; t.nextJob = job; // It is possible that we could get a wierd race condition with // notify()/wait() signalling on a thread being used by higher // level code. So we'd best use notifyAll(). t.notifyAll(); } if(logMINOR) synchronized(this) { Logger.minor(this, "Not starting: Jobs: " + jobMisses + " misses of " + jobCount + " starting urgently " + jobName); } return; } } @Override public synchronized int[] runningThreads() { int[] result = new int[runningThreads.length]; for(int i = 0; i < result.length; i++) result[i] = runningThreads[i] - waitingThreads[i].size(); return result; } @Override public synchronized int[] waitingThreads() { int[] result = new int[waitingThreads.length]; for(int i = 0; i < result.length; i++) result[i] = waitingThreads[i].size(); return result; } @Override public int getWaitingThreadsCount() { return waitingThreadsCount; } private static class Job { private final Runnable runnable; private final String name; Job(Runnable runnable, String name) { this.runnable = runnable; this.name = name; } } private class MyThread extends NativeThread { final String defaultName; volatile boolean alive = true; Job nextJob; final long threadNo; private boolean removed = false; public MyThread(String defaultName, Job firstJob, long threadCounter, int prio, boolean dontCheckRenice) { super(defaultName, prio, dontCheckRenice); this.defaultName = defaultName; threadNo = threadCounter; nextJob = firstJob; } @Override public void realRun() { int nativePriority = getNativePriority(); try { innerRun(nativePriority); } finally { if(!removed) { synchronized(PooledExecutor.this) { runningThreads[nativePriority - 1]--; } } } } private void innerRun(int nativePriority) { long ranJobs = 0; while(true) { Job job; synchronized(this) { job = nextJob; nextJob = null; } if(job == null) { synchronized(PooledExecutor.this) { waitingThreads[nativePriority - 1].add(this); waitingThreadsCount++; } synchronized(this) { if(nextJob == null) { this.setName(defaultName); try { wait(TIMEOUT); } catch(InterruptedException e) { // Ignore } } } synchronized(PooledExecutor.this) { if (waitingThreads[nativePriority - 1].remove(this)) waitingThreadsCount--; synchronized(this) { job = nextJob; nextJob = null; // FIXME Fortify thinks this is double-checked locking. IMHO this is a false alarm. if(job == null) alive = false; } if(!alive) { runningThreads[nativePriority - 1]--; if(logMINOR) Logger.minor(this, "Exiting having executed " + ranJobs + " jobs : " + this); removed = true; return; } } } // Run the job try { setName(job.name + "(" + threadNo + ")"); job.runnable.run(); } catch(Throwable t) { Logger.error(this, "Caught " + t + " running job " + job, t); } ranJobs++; } } } }