/**
* Copyright (c) 2009-2011, The HATS Consortium. All rights reserved.
* This file is licensed under the terms of the Modified BSD License.
*/
package abs.backend.java.scheduling;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Logger;
import abs.backend.java.lib.runtime.ABSDeadlockException;
import abs.backend.java.lib.runtime.ABSException;
import abs.backend.java.lib.runtime.ABSFut;
import abs.backend.java.lib.runtime.ABSRuntime;
import abs.backend.java.lib.runtime.Logging;
import abs.backend.java.lib.runtime.Task;
import abs.backend.java.scheduling.SimpleTaskScheduler.SimpleSchedulerThread;
public class GlobalScheduler {
private static Logger logger = Logging.getLogger(GlobalScheduler.class.getName());
private final ScheduleOptions options = new ScheduleOptions();
private final GlobalSchedulingStrategy strategy;
private final ABSRuntime runtime;
private volatile boolean isShutdown;
private final AtomicInteger counter = new AtomicInteger();
/**
* Holds a list of threads that wait for other threads to complete
* their next step
* protected by lock of this
*/
private final ArrayList<SimpleLock> nextStepWaitStack = new ArrayList<SimpleLock>();
public GlobalScheduler(ABSRuntime runtime, GlobalSchedulingStrategy strategy) {
this.strategy = strategy;
this.runtime = runtime;
}
private long totalNumChoices = 0;
public void doNextScheduleStep() {
if (isShutdown) return;
int i = counter.incrementAndGet();
logger.finest("==="+i+": Do next step...");
ScheduleAction next = null;
synchronized (this) {
if (nextStepWaitStack.size() > 0) {
SimpleLock l = nextStepWaitStack.remove(nextStepWaitStack.size()-1);
logger.finest("==="+i+": Ignored step, awaking thread ");
l.unlock();
return;
}
if (options.isEmpty()) {
List<SimpleSchedulerThread> activeThreads = runtime.getThreadManager().getAllCopyOf(SimpleSchedulerThread.class);
if (!activeThreads.isEmpty()) {
Set<Task<?>> suspendedTasks = new HashSet<Task<?>>();
for (SimpleSchedulerThread st : activeThreads) {
Task<?> t = st.getExecutingTask().task;
if (t != null && !t.isFinished()) {
suspendedTasks.add(t);
}
}
// Need to filter out currentTask (that is finishing)
Thread tt = Thread.currentThread();
if (tt instanceof SimpleSchedulerThread) {
Task<?> currT = ((SimpleSchedulerThread)tt).getExecutingTask().task;
suspendedTasks.remove(currT);
}
if (!suspendedTasks.isEmpty()) {
ABSException ex = new ABSDeadlockException();
for (Task<?> t : suspendedTasks) {
t.setException(ex);
}
runtime.handleABSException(suspendedTasks.iterator().next(), ex);
return;
}
}
logger.info("No steps left. Program finished");
logger.info("Total number of global choices: " + totalNumChoices);
if (totalNumChoices == 0) {
logger.info("Program is deterministic!");
}
return;
}
totalNumChoices += options.numOptions() - 1;
logger.finest("==="+i+" Choose next action...");
next = strategy.choose(options);
logger.finest("==="+i+" Action " + next + " choosen");
options.removeOption(next);
logger.finest("==="+i+" Executing Action " + next);
}
if (isShutdown) return;
int j = counter.intValue();
if (i != j)
logger.warning("#### Interleaving detected "+i+" != "+j);
next.execute();
logger.finest("==="+i+" Action " + next + " was executed.");
}
public void stepTask(Task<?> task) throws InterruptedException {
if (isShutdown) return;
ScheduleAction a = new StepTask(task);
synchronized (this) {
options.addOption(a);
doNextScheduleStep();
}
a.await();
}
public synchronized void addAction(ScheduleAction action) {
options.addOption(action);
}
private synchronized void ignoreNextStep(SimpleLock l) {
nextStepWaitStack.add(l);
}
static class SimpleLock {
private boolean locked;
synchronized void unlock() {
locked = false;
notifyAll();
}
synchronized void awaitUnlocked() {
while (locked) {
try {
logger.finest("Awaiting next step...");
wait();
} catch (InterruptedException e) {
logger.fine("was interrupted");
Thread.currentThread().interrupt();
break;
}
}
}
}
/**
* Handling a get in the global scheduler is pretty tricky.
* The following is going on here:
* the current task does a .get on the given future.
* 2 Cases:
*
* 1. Future is ready, that is easy, we just continue
*
* 2. Future is not ready
*
* this case is difficult, because we have to
* 1. block the current thread
* 2. schedule some other task that is ready by choosing one by the
* global scheduler
* 3. when the future is resolved the blocked thread must be awaked,
* BUT we must ensure that only one thread is calling the
* doNextScheduleStep method.
* For this reason there is an ignoreNextStep field that
* can be set to ignore the next call to doNextScheduleStep
*
*
* what we do now is to create a Waker object
* this waker object is added to the future we are waiting for, so
* that this future can inform us when it is resolved.
* we then schedule the next task by calling runtime.doNextStep()
* and after that we suspend the thread to be awaked later by the
* future, see Waker class for further docu
*
* @param fut
*/
public void handleGet(ABSFut<?> fut) {
// note that this code does only work in the presence of global
// scheduling,
// otherwise it would not be thread-safe
if (fut.isResolved()) {
return;
}
Waker w = new Waker(this);
fut.addWaitingThread(w);
runtime.doNextStep();
logger.finest("future waiting");
w.await();
}
private static class Waker implements GuardWaiter {
boolean awaken;
final GlobalScheduler globalScheduler;
public Waker(GlobalScheduler scheduler) {
globalScheduler = scheduler;
}
public synchronized void awake() {
awaken = true;
notify();
}
public synchronized void await() {
while (!awaken) {
try {
wait();
} catch (InterruptedException e) {
logger.fine("received interrupt exception");
Thread.currentThread().interrupt();
break;
}
}
logger.finest("task awaked");
}
@Override
public void checkGuard() {
logger.finest("checking guard...");
SimpleLock l = new SimpleLock();
globalScheduler.ignoreNextStep(l);
awake();
logger.finest("await next step");
// we are now waiting for the awaked thread to do the
// call to doNextScheduleStep, so that there are no
// two parallel threads running
l.awaitUnlocked();
}
}
public void shutdown() {
isShutdown = true;
}
}