/** * 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.Collections; import java.util.Iterator; import java.util.List; import java.util.Random; import java.util.concurrent.atomic.AtomicLong; import java.util.logging.Level; import java.util.logging.Logger; import abs.backend.java.lib.runtime.ABSAndGuard; import abs.backend.java.lib.runtime.ABSFutureGuard; import abs.backend.java.lib.runtime.ABSGuard; import abs.backend.java.lib.runtime.ABSInitObjectCall; import abs.backend.java.lib.runtime.ABSRuntime; import abs.backend.java.lib.runtime.ABSThread; import abs.backend.java.lib.runtime.ABSThreadManager; import abs.backend.java.lib.runtime.COG; import abs.backend.java.lib.runtime.Config; import abs.backend.java.lib.runtime.Logging; import abs.backend.java.lib.runtime.SystemTerminatedException; import abs.backend.java.lib.runtime.Task; import abs.backend.java.observing.SystemObserver; import abs.backend.java.observing.TaskObserver; import abs.backend.java.observing.TaskSchedulerView; import abs.backend.java.observing.TaskView; import abs.backend.java.scheduling.SimpleTaskScheduler.TaskInfo; /** * A simple scheduler that is not the most efficient one, but is easy to * understand and also makes it easy to override the scheduling behavior * * @author Jan Schäfer * */ public class SimpleTaskScheduler implements TaskScheduler { private final AtomicLong idCounter = new AtomicLong(); static Logger logger = Logging.getLogger("scheduler"); private final ABSThreadManager threadManager; private final ScheduableTasksFilter scheduableTasksFilter; public class TaskInfo { /** * A COG-wide unique id, that is increment for each new task. */ public long id = idCounter.incrementAndGet(); public final Task<?> task; /** * is null unless this task as a fixed assigned thread */ public SimpleSchedulerThread thread; /** * is null unless this task is waiting on a guard suspended tasks are * always waiting on a guard */ public ABSGuard guard; /** * stores whether this task has already been activated once */ public boolean hasBeenActivated = false; public TaskInfo(Task<?> task) { this.task = task; } public boolean isSchedulable() { return !isSuspended() || guard.isTrue(); } public boolean isSuspended() { return guard != null; } public void makeReady() { guard = null; } public void suspend(ABSGuard g) { guard = g; } public String toString() { return "Task " + task.getID(); } } protected TaskSchedulingStrategy schedulingStrategy; /** * Holds a list of all ready tasks. Ready tasks are tasks that can * definitely be scheduled. Note that this list does not include tasks that * are suspended on an unstable guard, i.e., a guard that may become false * after it was true. Such tasks are in the suspendedTasks list. * * Invariant: for all task in readyTasks: task.isSuspended() == false */ protected final List<TaskInfo> readyTasks = new ArrayList<TaskInfo>(); /** * Holds a list of all suspended tasks Invariant: for all task in * suspendedTasks: task.isSuspended() == true */ protected final List<TaskInfo> suspendedTasks = new ArrayList<TaskInfo>(); /** * Holds the currently active task or is null if there is no active task */ protected TaskInfo activeTask; protected final COG cog; private final ABSRuntime runtime; public SimpleTaskScheduler(COG cog, TaskSchedulingStrategy strat, ABSRuntime runtime, ABSThreadManager m, ScheduableTasksFilter filter) { this.threadManager = m; this.cog = cog; if (strat != null) { // use user-defined strategy this.schedulingStrategy = strat; } else if (runtime.getTaskSchedulingStrategy() != null) { // use global strategy this.schedulingStrategy = runtime.getTaskSchedulingStrategy(); } else { // use random scheduling strategy as a fallback this.schedulingStrategy = new RandomSchedulingStrategy(runtime.getRandom()); } logger.config("TaskSchedulingStrategy: " + this.schedulingStrategy.getClass().getName()); this.runtime = runtime; this.scheduableTasksFilter = filter; } protected void taskDeadlocked() { logger.warning("Task "+activeTask+" deadlocked"); runtime.doNextStep(); } protected void taskFinished() { logger.finest("Task finished getting monitor..."); TaskInfo finishedTask = null; synchronized (this) { logger.finest("got monitor"); finishedTask = activeTask; finishedTask.task.setFinished(true); activeTask = null; if (suspendedTasks.size() + readyTasks.size() > 0) { logger.finest("calling schedule..."); schedule(); logger.finest("schedule called"); } } logger.finest("do next step"); // we now have to wait for all tasks that waited for the future // of this task to give them the opportunity to add a schedule action // to the global scheduler, before we do this step runtime.doNextStep(); logger.finest("next step done"); } @Override public synchronized void addTask(final Task<?> task) { readyTasks.add(new TaskInfo(task)); if (view != null) view.taskAdded(task.getView()); if (activeTask == null) { if (runtime.hasGlobalScheduler() && (task.getCall() instanceof ABSInitObjectCall)) { runtime.addScheduleAction(new ActivateTask(cog,task) { @Override public void execute() { synchronized (SimpleTaskScheduler.this) { TaskInfo ti = readyTasks.remove(0); activateTask(ti); } } }); } else { schedule(); } } } class SimpleSchedulerThread extends ABSThread implements GuardWaiter { private final TaskInfo executingTask; private boolean active; private ABSGuard guard; public SimpleSchedulerThread(TaskInfo activeTask) { super(threadManager); this.executingTask = activeTask; setName("ABS Scheduler Thread of " + cog.toString()); setCOG(cog); } public TaskInfo getExecutingTask() { return executingTask; } public List<Task<?>> getSuspendedTasks() { List<Task<?>> result = new ArrayList<Task<?>>(); for (TaskInfo ti : suspendedTasks) { result.add(ti.task); } return result; } @Override public void run() { try { active = true; executingTask.task.run(); if (executingTask.task.isDeadlocked()) taskDeadlocked(); else taskFinished(); } catch (SystemTerminatedException e){ } finally { finished(); } } public void checkGuard() { // have to take locks in that order to prevent deadlocks // because schedule might get called synchronized (SimpleTaskScheduler.this) { synchronized (this) { logger.finest(executingTask + " checking guard"); if (guard.isTrue() && guard.staysTrue()) { logger.finest(executingTask + " got monitor"); suspendedTasks.remove(executingTask); readyTasks.add(executingTask); executingTask.makeReady(); if (view != null) view.taskReady(executingTask.task.getView()); if (activeTask == null) { logger.finest(executingTask + " scheduling myself"); schedule(); } } } } } synchronized void setGuard(ABSGuard g) { logger.finest(executingTask + " awaiting " + g); active = false; this.guard = g; logger.finest(executingTask + " registering at threads..."); boolean wasAdded = registerAtThreads(g); if (!wasAdded) { logger.fine(this+" was not added to guard "+g); } } void await(ABSGuard g) { logger.finest(executingTask + " next step done going into monitor"); synchronized (this) { try { logger.finest(executingTask + " waiting to be resumed"); while (!active) { wait(); } } catch (InterruptedException e) { wasInterrupted(e); } logger.finest(executingTask + " resumed"); active = true; } } private boolean registerAtThreads(ABSGuard g) { boolean wasAdded = false; if (g instanceof ABSFutureGuard) { ABSFutureGuard fg = (ABSFutureGuard) g; wasAdded = fg.fut.addWaitingThread(this); logger.finest(executingTask + " was "+(wasAdded ? "" :"NOT ")+"added to " + fg.fut); } else if (g instanceof ABSAndGuard) { ABSAndGuard ag = (ABSAndGuard) g; wasAdded = registerAtThreads(ag.getLeftGuard()); wasAdded |= registerAtThreads(ag.getRightGuard()); } return wasAdded; } public synchronized void awake() { active = true; notify(); logger.fine(executingTask.toString() + " awaked"); } } private synchronized void schedule() { if (runtime.hasGlobalScheduler()) { if (suspendedTasks.isEmpty() && readyTasks.isEmpty()) return; logger.finest("Adding scheduling action..."); runtime.addScheduleAction(new ScheduleTask(cog) { @Override public void execute() { logger.finest("Calling do schedule"); doSchedule(); } }); logger.finest("Done"); } else { doSchedule(); } } private void doSchedule() { logger.finest("Executing doSchedule..."); List<TaskInfo> choices = getSchedulableTasks(); if (logger.isLoggable(Level.INFO)) logger.info("COG " + cog.getID() + " scheduling choices: " + choices); if (choices.isEmpty()) { logger.info("Choices are empty!"); runtime.doNextStep(); return; } TaskInfo nextTask = schedule(choices); nextTask.hasBeenActivated = true; synchronized (this) { if (nextTask.isSuspended()) { suspendedTasks.remove(nextTask); nextTask.makeReady(); } else { readyTasks.remove(nextTask); } activateTask(nextTask); } } private synchronized List<TaskInfo> getSchedulableTasks() { List<TaskInfo> suspendedTasksWithSatisfiedGuards = unsuspendTasks(); List<TaskInfo> choices = new ArrayList<TaskInfo>(readyTasks); choices.addAll(suspendedTasksWithSatisfiedGuards); List<TaskInfo> choicesFiltered = scheduableTasksFilter.filter(choices); return choicesFiltered; } private synchronized void activateTask(TaskInfo nextTask) { activeTask = nextTask; if (activeTask.thread != null) { logger.info("COG " + cog.getID() + " awaking " + activeTask); activeTask.thread.awake(); } else { logger.info("COG " + cog.getID() + " creating " + activeTask); activeTask.thread = new SimpleSchedulerThread(activeTask); activeTask.thread.start(); activeTask.task.setStart(System.currentTimeMillis()); } } private synchronized List<TaskInfo> unsuspendTasks() { List<TaskInfo> tasksWithSatisfiedGuards = new ArrayList<TaskInfo>(0); Iterator<TaskInfo> it = suspendedTasks.iterator(); while (it.hasNext()) { TaskInfo task = it.next(); if (task.guard.isTrue()) { if (task.guard.staysTrue()) { readyTasks.add(task); task.makeReady(); it.remove(); } else { tasksWithSatisfiedGuards.add(task); } } } return tasksWithSatisfiedGuards; } protected TaskInfo schedule(List<TaskInfo> schedulableTasks) { return schedulingStrategy.schedule(this, schedulableTasks); } private volatile View view; private final Object viewCreatorLock = new Object(); @Override public TaskSchedulerView getView() { synchronized (viewCreatorLock) { if (view == null) view = new View(); return view; } } @Override public synchronized Task<?> getActiveTask() { if (activeTask == null) return null; return activeTask.task; } @Override public void await(ABSGuard g) { SimpleSchedulerThread thread = null; TaskInfo newTask = null; TaskInfo currentTask = null; synchronized (this) { if (activeTask == null) { // while shutting down the activeTask might bee null return; } currentTask = activeTask; thread = currentTask.thread; currentTask.suspend(g); suspendedTasks.add(currentTask); activeTask = null; if (view != null) view.taskSuspended(currentTask.task.getView(), g); thread.setGuard(g); if (g.isTrue() || (suspendedTasks.size() + readyTasks.size()) > 1) { logger.fine("issuing a schedule"); schedule(); } } runtime.doNextStep(); synchronized (this) { newTask = activeTask; } if (newTask != currentTask) { thread.await(g); } if (view != null) view.taskResumed(currentTask.task.getView(), g); } public static TaskSchedulerFactory getFactory() { return new TaskSchedulerFactory() { @Override public TaskScheduler createTaskScheduler(ABSRuntime runtime, COG cog, ABSThreadManager m, ScheduableTasksFilter filter) { return new SimpleTaskScheduler(cog, null, runtime, m, filter); } }; } @Override public COG getCOG() { return cog; } public TaskSchedulingStrategy getSchedulingStrategy() { return schedulingStrategy; } // Enable changing the task scheduling strategy at runtime public void setSchedulingStrategy(TaskSchedulingStrategy strat) { schedulingStrategy = strat; } private class View extends AbstractTaskSchedulerView { @Override public List<TaskView> getSchedulableTasks() { synchronized (SimpleTaskScheduler.this) { ArrayList<TaskView> result = new ArrayList<TaskView>(); if (getActiveTask() != null) { result.add(getActiveTask()); return result; } for (TaskInfo t : SimpleTaskScheduler.this.getSchedulableTasks()) { result.add(t.task.getView()); } return result; } } @Override public List<TaskView> getReadyTasks() { synchronized (SimpleTaskScheduler.this) { ArrayList<TaskView> result = new ArrayList<TaskView>(); for (TaskInfo t : readyTasks) { result.add(t.task.getView()); } return result; } } @Override public List<TaskView> getSuspendedTasks() { synchronized (SimpleTaskScheduler.this) { ArrayList<TaskView> result = new ArrayList<TaskView>(); for (TaskInfo t : suspendedTasks) { result.add(t.task.getView()); } return result; } } @Override public TaskView getActiveTask() { Task<?> activeTask = SimpleTaskScheduler.this.getActiveTask(); if (activeTask == null) return null; return activeTask.getView(); } } }