package freenet.support;
import static java.util.concurrent.TimeUnit.MINUTES;
import java.util.ArrayDeque;
import freenet.node.NodeStats;
import freenet.node.PrioRunnable;
import freenet.support.Logger.LogLevel;
import freenet.support.io.NativeThread;
public class PrioritizedSerialExecutor implements Executor {
private static volatile boolean logMINOR;
static {
Logger.registerLogThresholdCallback(new LogThresholdCallback() {
@Override
public void shouldUpdate() {
logMINOR = Logger.shouldLog(LogLevel.MINOR, this);
}
});
}
private final ArrayDeque<Runnable>[] jobs;
private final int priority;
private final int defaultPriority;
private boolean waiting;
private final boolean invertOrder;
private String name;
private Executor realExecutor;
private boolean running;
private final ExecutorIdleCallback callback;
private static final long DEFAULT_JOB_TIMEOUT = MINUTES.toMillis(5);
private final long jobTimeout;
private final Runner runner = new Runner();
private final NodeStats statistics;
class Runner implements PrioRunnable {
Thread current;
@Override
public int getPriority() {
return priority;
}
@Override
public void run() {
synchronized(jobs) {
if(current != null) {
if(current.isAlive()) {
Logger.error(this, "Already running a thread for "+this+" !!", new Exception("error"));
return;
}
}
current = Thread.currentThread();
}
try {
boolean calledIdleCallback = false;
while(true) {
Runnable job = null;
synchronized(jobs) {
job = checkQueue();
if(job == null) {
waiting = true;
try {
//NB: notify only on adding work or this quits early.
jobs.wait(jobTimeout);
} catch (InterruptedException e) {
// Ignore
}
waiting=false;
job = checkQueue();
if(job == null) {
if(calledIdleCallback || callback == null) {
running=false;
current = null;
return;
}
}
}
}
if(job == null) {
try {
callback.onIdle();
} catch (Throwable t) {
Logger.error(this, "Idle callback failed: "+t, t);
}
calledIdleCallback = true;
continue;
}
calledIdleCallback = false;
try {
if(logMINOR)
Logger.minor(this, "Running job "+job);
long start = System.currentTimeMillis();
job.run();
long end = System.currentTimeMillis();
if(logMINOR) {
Logger.minor(this, "Job "+job+" took "+(end-start)+"ms");
}
if(statistics != null) {
statistics.reportDatabaseJob(job.toString(), end-start);
}
} catch (Throwable t) {
Logger.error(this, "Caught "+t, t);
Logger.error(this, "While running "+job+" on "+this);
}
}
} finally {
synchronized(jobs) {
current = null;
running = false;
}
}
}
private Runnable checkQueue() {
if(!invertOrder) {
for(int i=0;i<jobs.length;i++) {
if(!jobs[i].isEmpty()) {
if(logMINOR)
Logger.minor(this, "Chosen job at priority "+i);
return jobs[i].removeFirst();
}
}
} else {
for(int i=jobs.length-1;i>=0;i--) {
if(!jobs[i].isEmpty()) {
if(logMINOR)
Logger.minor(this, "Chosen job at priority "+i);
return jobs[i].removeFirst();
}
}
}
return null;
}
};
/**
*
* @param priority
* @param internalPriorityCount
* @param defaultPriority
* @param invertOrder Set if the priorities are thread priorities. Unset if they are request priorities. D'oh!
*/
public PrioritizedSerialExecutor(int priority, int internalPriorityCount, int defaultPriority, boolean invertOrder, long jobTimeout, ExecutorIdleCallback callback, NodeStats statistics) {
@SuppressWarnings("unchecked") ArrayDeque<Runnable>[] jobs = new ArrayDeque[internalPriorityCount];
for (int i=0;i<jobs.length;i++) {
jobs[i] = new ArrayDeque<Runnable>();
}
this.jobs = jobs;
this.priority = priority;
this.defaultPriority = defaultPriority;
this.invertOrder = invertOrder;
this.jobTimeout = jobTimeout;
this.callback = callback;
this.statistics = statistics;
}
public PrioritizedSerialExecutor(int priority, int internalPriorityCount, int defaultPriority, boolean invertOrder) {
this(priority, internalPriorityCount, defaultPriority, invertOrder, DEFAULT_JOB_TIMEOUT, null, null);
}
public void start(Executor realExecutor, String name) {
this.realExecutor=realExecutor;
this.name=name;
synchronized (jobs) {
boolean empty = true;
for(ArrayDeque<Runnable> l: jobs) {
if(!l.isEmpty()) {
empty = false;
break;
}
}
if(!empty)
reallyStart();
}
}
private void reallyStart() {
synchronized(jobs) {
if(running) {
Logger.error(this, "Not reallyStart()ing: ALREADY RUNNING", new Exception("error"));
return;
}
running=true;
if(logMINOR) Logger.minor(this, "Starting thread... "+name+" : "+runner, new Exception("debug"));
realExecutor.execute(runner, name);
}
}
@Override
public void execute(Runnable job) {
execute(job, "<noname>");
}
@Override
public void execute(Runnable job, String jobName) {
int prio = defaultPriority;
if(job instanceof PrioRunnable)
prio = ((PrioRunnable) job).getPriority();
execute(job, prio, jobName);
}
public void execute(Runnable job, int prio, String jobName) {
synchronized(jobs) {
if(logMINOR)
Logger.minor(this, "Queueing "+jobName+" : "+job+" priority "+prio+", executor state: running="+running+" waiting="+waiting);
jobs[prio].addLast(job);
jobs.notifyAll();
if(!running && realExecutor != null) {
reallyStart();
}
}
}
public void executeNoDupes(Runnable job, int prio, String jobName) {
synchronized(jobs) {
if(jobs[prio].contains(job)) {
if(logMINOR)
Logger.minor(this, "Not queueing job: Job already queued: "+job);
return;
}
if(logMINOR)
Logger.minor(this, "Queueing "+jobName+" : "+job+" priority "+prio+", executor state: running="+running+" waiting="+waiting);
jobs[prio].addLast(job);
jobs.notifyAll();
if(!running && realExecutor != null) {
reallyStart();
}
}
}
@Override
public void execute(Runnable job, String jobName, boolean fromTicker) {
execute(job, jobName);
}
@Override
public int[] runningThreads() {
int[] retval = new int[NativeThread.JAVA_PRIORITY_RANGE+1];
if (running)
retval[priority] = 1;
return retval;
}
@Override
public int[] waitingThreads() {
int[] retval = new int[NativeThread.JAVA_PRIORITY_RANGE+1];
synchronized(jobs) {
if(waiting)
retval[priority] = 1;
}
return retval;
}
public boolean onThread() {
Thread running = Thread.currentThread();
synchronized(jobs) {
if(runner == null) return false;
return runner.current == running;
}
}
public int[] getQueuedJobsCountByPriority() {
int[] retval = new int[jobs.length];
synchronized(jobs) {
for(int i=0;i<retval.length;i++)
retval[i] = jobs[i].size();
}
return retval;
}
public Runnable[][] getQueuedJobsByPriority() {
final Runnable[][] ret = new Runnable[jobs.length][];
synchronized(jobs) {
for(int i=0; i < jobs.length; ++i) {
ret[i] = jobs[i].toArray(new Runnable[jobs[i].size()]);
}
}
return ret;
}
public int getQueueSize(int priority) {
synchronized(jobs) {
return jobs[priority].size();
}
}
@Override
public int getWaitingThreadsCount() {
synchronized(jobs) {
return (waiting ? 1 : 0);
}
}
public boolean anyQueued() {
synchronized(jobs) {
for(int i=0;i<jobs.length;i++)
if(jobs[i].size() > 0) return true;
}
return false;
}
}