/*******************************************************************************
* gMix open source project - https://svs.informatik.uni-hamburg.de/gmix/
* Copyright (C) 2014 SVS
*
* 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 3 of the License, or
* (at your option) any later version.
*
* 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.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*******************************************************************************/
package staticContent.evaluation.loadGenerator.scheduler;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import staticContent.framework.config.Settings;
// warning: this scheduler does not allow scheduling tasks with an execution time earlier than the task with the latest execution time already scheduled
public class InOrderYieldWaitScheduler<E> extends Scheduler<E> {
private final static long SLEEP_PRECISION = TimeUnit.MILLISECONDS.toNanos(2); // TODO: determine for current machine
private final static long INIT_TIME = System.nanoTime();
private LinkedBlockingQueue<QueueEntry> eventQueue = new LinkedBlockingQueue<QueueEntry>();
private long latestExecutionScheduled = Long.MIN_VALUE;
private QueueEntry lastEntry = null;
private boolean isFirstEntry = true;
private boolean shutdownRequested = false;
// @tolerance in microsec (uses microsec as nanosec would imply a higher accuracy than available)
public InOrderYieldWaitScheduler(Settings settings) {
super(settings);
}
@Override
public void shutdown() {
this.shutdownRequested = true;
}
@Override
public synchronized void executeAt(long executionTime, ScheduleTarget<E> scheduleTarget, E attachment) {
if (isFirstEntry) {
isFirstEntry = false;
new ExecutorThread().start();
}
if (executionTime < latestExecutionScheduled)
throw new RuntimeException("scheduleTargets must be added in chronological order");
this.latestExecutionScheduled = executionTime;
this.lastEntry = new QueueEntry(executionTime, scheduleTarget, attachment);
this.eventQueue.add(lastEntry);
}
@Override
public synchronized void executeIn(long delayInNanoSec, ScheduleTarget<E> scheduleTarget, E attachment) {
executeAt(now()+delayInNanoSec, scheduleTarget, attachment);
}
@Override
public long now() {
return System.nanoTime() - INIT_TIME;
}
@Override
public void notifyOnOutputOfLast(ScheduleTarget<E> notifyTarget) {
this.lastEntry.notifyTarget = notifyTarget;
}
private void sleepNanos(long nanoDuration) {
// see http://andy-malakov.blogspot.de/2010/06/alternative-to-threadsleep.html
final long end = now() + nanoDuration;
long timeLeft = nanoDuration;
try {
do {
if (timeLeft > SLEEP_PRECISION)
Thread.sleep(1);
else
Thread.yield();
timeLeft = end - now();
if (Thread.interrupted())
throw new InterruptedException();
} while (timeLeft > 0);
} catch (InterruptedException e) {
sleepNanos(end - now());
}
}
private class ExecutorThread extends Thread {
@Override
public void run() {
exit: while (true) {
QueueEntry task = null;
while (true) { // get next event from queue (interrupt from time to time to check if a shutdown was requested)
try {
task = eventQueue.poll(2, TimeUnit.SECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (task != null)
break;
else if (shutdownRequested)
break exit;
}
if (now() >= task.executionTime) { // execute now
warnIfDelayed(now() - task.executionTime);
if (task.notifyTarget != null)
task.notifyTarget.execute(task.attachment);
task.scheduleTarget.execute(task.attachment);
} else { // wait till execute
sleepNanos(task.executionTime - now());
warnIfDelayed(now() - task.executionTime);
if (task.notifyTarget != null)
task.notifyTarget.execute(task.attachment);
task.scheduleTarget.execute(task.attachment);
}
}
}
}
private class QueueEntry {
long executionTime;
ScheduleTarget<E> scheduleTarget;
E attachment;
ScheduleTarget<E> notifyTarget;
private QueueEntry(long executionTime, ScheduleTarget<E> scheduleTarget, E attachment) {
this.executionTime = executionTime;
this.scheduleTarget = scheduleTarget;
this.attachment = attachment;
}
}
}