/*******************************************************************************
* 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.LinkedList;
import java.util.concurrent.TimeUnit;
import staticContent.framework.config.Settings;
public class YieldWaitScheduler<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 LinkedList<QueueEntry> eventQueue = new LinkedList<QueueEntry>();
private long latestExecutionScheduled = Long.MIN_VALUE;
private QueueEntry lastEntry = null;
private boolean isFirstEntry = true;
private boolean shutdownRequested = false;
private int positionTmp;
private volatile boolean interruptSleep = false;
private volatile boolean executorThreadWaiting = false;
private ExecutorThread executorThread;
// @tolerance in microsec (uses microsec as nanosec would imply a higher accuracy than available)
public YieldWaitScheduler(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;
this.executorThread = new ExecutorThread();
this.executorThread.start();
}
QueueEntry queueEntry = new QueueEntry(executionTime, scheduleTarget, attachment);
if (executionTime > this.latestExecutionScheduled) {
this.latestExecutionScheduled = executionTime;
this.lastEntry = queueEntry;
}
synchronized (executorThread) {
int position = findPosition(queueEntry);
if (position == 0)
interruptSleep = true;
this.eventQueue.add(position, queueEntry);
if (executorThreadWaiting) {
executorThreadWaiting = false;
executorThread.notifyAll();
}
}
}
@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 && !interruptSleep);
} catch (InterruptedException e) {
if (timeLeft > 0 && !interruptSleep)
sleepNanos(end - now());
}
}
private class ExecutorThread extends Thread {
@Override
public void run() {
exit: while (true) {
QueueEntry task = null;
synchronized (executorThread) {
while (eventQueue.size() == 0 && !shutdownRequested) {
executorThreadWaiting = true;
try {
executorThread.wait();
} catch (InterruptedException e) {
continue;
}
}
if (!shutdownRequested)
task = eventQueue.get(0);
else
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);
synchronized (executorThread) {
eventQueue.remove();
}
} else { // wait till execute
sleepNanos(task.executionTime - now());
if (shutdownRequested)
break exit;
synchronized (executorThread) {
task = eventQueue.remove(); // always use first queue element (may be another element after sleep)
if (interruptSleep)
interruptSleep = false;
}
warnIfDelayed(now() - task.executionTime);
if (task.notifyTarget != null)
task.notifyTarget.execute(task.attachment);
task.scheduleTarget.execute(task.attachment);
}
}
}
}
private class QueueEntry implements Comparable<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;
}
/**
* Implements the <code>Comparable</code> interface's <code>compareTo()
* </code> method. Compares this <code>Event</code> with the specified
* <code>Event</code> for order (criterion: alphabetic order of this
* <code>Event</code>'s <code>executionTime</code>. Returns a negative
* integer, zero, or a positive integer as this <code>Event</code> is
* less than, equal to, or greater than the specified <code>Event</code>.
*
* @param event The <code>Event</code> to be compared.
*
* @return -1, 0, or 1 as this <code>Event</code> is less than,
* equal to, or greater than the specified <code>Event
* </code>.
*/
@Override
public int compareTo(QueueEntry e) {
if (this.executionTime < e.executionTime)
return -1;
else if (this.executionTime > e.executionTime)
return 1;
else
return 0;
}
}
private int findPosition(QueueEntry entry) {
return findPosition(entry, 0, (eventQueue.size() - 1));
}
private int findPosition(QueueEntry entry, int startIndex, int endIndex) {
if (eventQueue.size() == 0) { // first message
return 0;
} else {
if (startIndex <= endIndex) {
int mid = (startIndex + endIndex) / 2;
switch (entry.compareTo(eventQueue.get(mid))) {
case -1:
positionTmp = mid;
findPosition(entry, startIndex, mid - 1);
break;
case 0:
positionTmp = mid;
startIndex = endIndex; // stop execution
break;
case 1:
positionTmp = mid + 1;
findPosition(entry, mid + 1, endIndex);
break;
}
}
}
return positionTmp;
}
/* @Override
public void notifyOnOutputOf(E e, ScheduleTarget<E> scheduleTarget) {
synchronized (executorThread) {
for (int i=eventQueue.size()-1; i>=0; i--) {
if (eventQueue.get(i).attachment == e) {
eventQueue.get(i).notifyTarget = scheduleTarget;
return;
}
}
throw new RuntimeException("not found");
}
}*/
}