diff --git a/jdeeco-core/.classpath b/jdeeco-core/.classpath
index 7efd7ac23..f5d25c2d7 100644
--- a/jdeeco-core/.classpath
+++ b/jdeeco-core/.classpath
@@ -13,7 +13,7 @@
-
+
diff --git a/jdeeco-core/src/cz/cuni/mff/d3s/deeco/model/runtime/custom/KnowledgeChangeTriggerExt.java b/jdeeco-core/src/cz/cuni/mff/d3s/deeco/model/runtime/custom/KnowledgeChangeTriggerExt.java
new file mode 100644
index 000000000..7c8c50656
--- /dev/null
+++ b/jdeeco-core/src/cz/cuni/mff/d3s/deeco/model/runtime/custom/KnowledgeChangeTriggerExt.java
@@ -0,0 +1,33 @@
+package cz.cuni.mff.d3s.deeco.model.runtime.custom;
+
+import cz.cuni.mff.d3s.deeco.model.runtime.api.KnowledgeChangeTrigger;
+import cz.cuni.mff.d3s.deeco.model.runtime.impl.KnowledgeChangeTriggerImpl;
+
+/**
+ * @author Tomas Bures
+ *
+ */
+public class KnowledgeChangeTriggerExt extends KnowledgeChangeTriggerImpl {
+
+ public KnowledgeChangeTriggerExt() {
+ super();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals(Object that) {
+ if (that != null && that instanceof KnowledgeChangeTrigger) {
+ return getKnowledgePath().equals(((KnowledgeChangeTrigger)that).getKnowledgePath());
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return getKnowledgePath().hashCode();
+ }
+}
diff --git a/jdeeco-core/src/cz/cuni/mff/d3s/deeco/model/runtime/custom/KnowledgePathExt.java b/jdeeco-core/src/cz/cuni/mff/d3s/deeco/model/runtime/custom/KnowledgePathExt.java
index e54d4c910..4f6c1335a 100644
--- a/jdeeco-core/src/cz/cuni/mff/d3s/deeco/model/runtime/custom/KnowledgePathExt.java
+++ b/jdeeco-core/src/cz/cuni/mff/d3s/deeco/model/runtime/custom/KnowledgePathExt.java
@@ -3,9 +3,11 @@
*/
package cz.cuni.mff.d3s.deeco.model.runtime.custom;
-import org.eclipse.emf.ecore.util.EcoreUtil;
+import java.util.LinkedList;
+import java.util.List;
import cz.cuni.mff.d3s.deeco.model.runtime.api.KnowledgePath;
+import cz.cuni.mff.d3s.deeco.model.runtime.api.PathNode;
import cz.cuni.mff.d3s.deeco.model.runtime.impl.KnowledgePathImpl;
/**
@@ -27,9 +29,19 @@ public KnowledgePathExt() {
@Override
public boolean equals(Object that) {
if (that instanceof KnowledgePath) {
- return EcoreUtil.equals(this, (KnowledgePath)that);
+ List thatNodes = new LinkedList<>(((KnowledgePath) that).getNodes());
+ List thisNodes = new LinkedList<>(nodes);
+ return thisNodes.equals(thatNodes);
}
-
return false;
}
+
+ @Override
+ public int hashCode() {
+ int code = 0;
+ for (PathNode node : getNodes()) {
+ code ^= node.hashCode();
+ }
+ return code;
+ }
}
diff --git a/jdeeco-core/src/cz/cuni/mff/d3s/deeco/model/runtime/custom/PathNodeCoordinatorExt.java b/jdeeco-core/src/cz/cuni/mff/d3s/deeco/model/runtime/custom/PathNodeCoordinatorExt.java
new file mode 100644
index 000000000..56657e41c
--- /dev/null
+++ b/jdeeco-core/src/cz/cuni/mff/d3s/deeco/model/runtime/custom/PathNodeCoordinatorExt.java
@@ -0,0 +1,31 @@
+package cz.cuni.mff.d3s.deeco.model.runtime.custom;
+
+import cz.cuni.mff.d3s.deeco.model.runtime.api.PathNodeCoordinator;
+import cz.cuni.mff.d3s.deeco.model.runtime.impl.PathNodeCoordinatorImpl;
+
+/**
+ * @author Tomas Bures
+ *
+ */
+public class PathNodeCoordinatorExt extends PathNodeCoordinatorImpl {
+
+ public PathNodeCoordinatorExt() {
+ super();
+ }
+
+ @Override
+ public boolean equals(Object that) {
+ return that != null && that instanceof PathNodeCoordinator;
+ }
+
+ @Override
+ public int hashCode() {
+ return 3872467;
+ }
+
+ @Override
+ public String toString() {
+ return "";
+ }
+
+}
diff --git a/jdeeco-core/src/cz/cuni/mff/d3s/deeco/model/runtime/custom/PathNodeFieldExt.java b/jdeeco-core/src/cz/cuni/mff/d3s/deeco/model/runtime/custom/PathNodeFieldExt.java
new file mode 100644
index 000000000..eb4c65aee
--- /dev/null
+++ b/jdeeco-core/src/cz/cuni/mff/d3s/deeco/model/runtime/custom/PathNodeFieldExt.java
@@ -0,0 +1,38 @@
+package cz.cuni.mff.d3s.deeco.model.runtime.custom;
+
+import cz.cuni.mff.d3s.deeco.model.runtime.api.PathNodeField;
+import cz.cuni.mff.d3s.deeco.model.runtime.impl.PathNodeFieldImpl;
+
+/**
+ * @author Michal Kit
+ *
+ */
+public class PathNodeFieldExt extends PathNodeFieldImpl {
+
+ public PathNodeFieldExt() {
+ super();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals(Object that) {
+ if (that != null && that instanceof PathNodeField) {
+ return ((PathNodeField) that).getName().equals(getName());
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return getName().hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return getName();
+ }
+}
diff --git a/jdeeco-core/src/cz/cuni/mff/d3s/deeco/model/runtime/custom/PathNodeMemberExt.java b/jdeeco-core/src/cz/cuni/mff/d3s/deeco/model/runtime/custom/PathNodeMemberExt.java
new file mode 100644
index 000000000..cd174c48a
--- /dev/null
+++ b/jdeeco-core/src/cz/cuni/mff/d3s/deeco/model/runtime/custom/PathNodeMemberExt.java
@@ -0,0 +1,31 @@
+package cz.cuni.mff.d3s.deeco.model.runtime.custom;
+
+import cz.cuni.mff.d3s.deeco.model.runtime.api.PathNodeMember;
+import cz.cuni.mff.d3s.deeco.model.runtime.impl.PathNodeMemberImpl;
+
+/**
+ * @author Tomas Bures
+ *
+ */
+public class PathNodeMemberExt extends PathNodeMemberImpl {
+
+ public PathNodeMemberExt() {
+ super();
+ }
+
+ @Override
+ public boolean equals(Object that) {
+ return that != null && that instanceof PathNodeMember;
+ }
+
+ @Override
+ public int hashCode() {
+ return 1847356;
+ }
+
+ @Override
+ public String toString() {
+ return "";
+ }
+
+}
diff --git a/jdeeco-core/src/cz/cuni/mff/d3s/deeco/model/runtime/custom/PeriodicTriggerExt.java b/jdeeco-core/src/cz/cuni/mff/d3s/deeco/model/runtime/custom/PeriodicTriggerExt.java
new file mode 100644
index 000000000..fa932fba8
--- /dev/null
+++ b/jdeeco-core/src/cz/cuni/mff/d3s/deeco/model/runtime/custom/PeriodicTriggerExt.java
@@ -0,0 +1,33 @@
+package cz.cuni.mff.d3s.deeco.model.runtime.custom;
+
+import cz.cuni.mff.d3s.deeco.model.runtime.api.PeriodicTrigger;
+import cz.cuni.mff.d3s.deeco.model.runtime.impl.PeriodicTriggerImpl;
+
+/**
+ * @author Tomas Bures
+ *
+ */
+public class PeriodicTriggerExt extends PeriodicTriggerImpl {
+
+ public PeriodicTriggerExt() {
+ super();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals(Object that) {
+ if (that != null && that instanceof PeriodicTrigger) {
+ return getPeriod() == ((PeriodicTrigger)that).getPeriod();
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return (int)getPeriod();
+ }
+}
diff --git a/jdeeco-core/src/cz/cuni/mff/d3s/deeco/model/runtime/custom/RuntimeMetadataFactoryExt.java b/jdeeco-core/src/cz/cuni/mff/d3s/deeco/model/runtime/custom/RuntimeMetadataFactoryExt.java
index 95cb6a113..cfc3d131a 100644
--- a/jdeeco-core/src/cz/cuni/mff/d3s/deeco/model/runtime/custom/RuntimeMetadataFactoryExt.java
+++ b/jdeeco-core/src/cz/cuni/mff/d3s/deeco/model/runtime/custom/RuntimeMetadataFactoryExt.java
@@ -3,7 +3,12 @@
*/
package cz.cuni.mff.d3s.deeco.model.runtime.custom;
+import cz.cuni.mff.d3s.deeco.model.runtime.api.KnowledgeChangeTrigger;
import cz.cuni.mff.d3s.deeco.model.runtime.api.KnowledgePath;
+import cz.cuni.mff.d3s.deeco.model.runtime.api.PathNodeCoordinator;
+import cz.cuni.mff.d3s.deeco.model.runtime.api.PathNodeField;
+import cz.cuni.mff.d3s.deeco.model.runtime.api.PathNodeMember;
+import cz.cuni.mff.d3s.deeco.model.runtime.api.PeriodicTrigger;
import cz.cuni.mff.d3s.deeco.model.runtime.impl.RuntimeMetadataFactoryImpl;
/**
@@ -24,6 +29,22 @@ public RuntimeMetadataFactoryExt() {
super();
}
+ /* (non-Javadoc)
+ * @see cz.cuni.mff.d3s.deeco.model.runtime.impl.RuntimeMetadataFactoryImpl#createPeriodicTrigger()
+ */
+ @Override
+ public PeriodicTrigger createPeriodicTrigger() {
+ return new PeriodicTriggerExt();
+ }
+
+ /* (non-Javadoc)
+ * @see cz.cuni.mff.d3s.deeco.model.runtime.impl.RuntimeMetadataFactoryImpl#createKnowledgeChangeTrigger()
+ */
+ @Override
+ public KnowledgeChangeTrigger createKnowledgeChangeTrigger() {
+ return new KnowledgeChangeTriggerExt();
+ }
+
/* (non-Javadoc)
* @see cz.cuni.mff.d3s.deeco.model.runtime.impl.RuntimeMetadataFactoryImpl#createKnowledgePath()
*/
@@ -32,5 +53,21 @@ public KnowledgePath createKnowledgePath() {
return new KnowledgePathExt();
}
+ @Override
+ public PathNodeField createPathNodeField() {
+ return new PathNodeFieldExt();
+ }
+
+ @Override
+ public PathNodeCoordinator createPathNodeCoordinator() {
+ return new PathNodeCoordinatorExt();
+ }
+
+ @Override
+ public PathNodeMember createPathNodeMember() {
+ return new PathNodeMemberExt();
+ }
+
+
// TODO: We might have also toString() method implemented for the KnowledgePath
}
diff --git a/jdeeco-core/src/cz/cuni/mff/d3s/deeco/scheduler/LocalTimeScheduler.java b/jdeeco-core/src/cz/cuni/mff/d3s/deeco/scheduler/LocalTimeScheduler.java
index f3fa2a015..693165a5a 100644
--- a/jdeeco-core/src/cz/cuni/mff/d3s/deeco/scheduler/LocalTimeScheduler.java
+++ b/jdeeco-core/src/cz/cuni/mff/d3s/deeco/scheduler/LocalTimeScheduler.java
@@ -114,7 +114,7 @@ private void stopTask(final Task task) {
TaskInfo ti = tasks.get(task);
if( ti != null && ti.state != States.RUNNING ){
- task.setTriggerListener(null);
+ task.unsetTriggerListener();
ti.timer.cancel();
diff --git a/jdeeco-core/src/cz/cuni/mff/d3s/deeco/scheduler/Scheduler.java b/jdeeco-core/src/cz/cuni/mff/d3s/deeco/scheduler/Scheduler.java
index 198b81edf..497990771 100644
--- a/jdeeco-core/src/cz/cuni/mff/d3s/deeco/scheduler/Scheduler.java
+++ b/jdeeco-core/src/cz/cuni/mff/d3s/deeco/scheduler/Scheduler.java
@@ -1,24 +1,24 @@
-package cz.cuni.mff.d3s.deeco.scheduler;
-
-import cz.cuni.mff.d3s.deeco.executor.ExecutionListener;
-import cz.cuni.mff.d3s.deeco.executor.Executor;
-import cz.cuni.mff.d3s.deeco.task.Task;
-
-
-/**
- * Interface Scheduler for LocalTimeScheduler(and others if needed)
- *
- * @author Andranik Muradyan
- *
- */
-public interface Scheduler extends ExecutionListener {
- public void start();
- public void stop();
- public void addTask( Task task );
- public void removeTask( Task task );
-
- public void executionFailed(Task task, Exception e);
- public void executionCompleted( Task task );
- public void setExecutor(Executor executor);
-
+package cz.cuni.mff.d3s.deeco.scheduler;
+
+import cz.cuni.mff.d3s.deeco.executor.ExecutionListener;
+import cz.cuni.mff.d3s.deeco.executor.Executor;
+import cz.cuni.mff.d3s.deeco.task.Task;
+
+
+/**
+ * Interface Scheduler for LocalTimeScheduler(and others if needed)
+ *
+ * @author Andranik Muradyan
+ *
+ */
+public interface Scheduler extends ExecutionListener {
+ public void start();
+ public void stop();
+ public void addTask( Task task );
+ public void removeTask( Task task );
+
+ public void executionFailed(Task task, Exception e);
+ public void executionCompleted( Task task );
+ public void setExecutor(Executor executor);
+
}
\ No newline at end of file
diff --git a/jdeeco-core/src/cz/cuni/mff/d3s/deeco/scheduler/SingleThreadedScheduler.java b/jdeeco-core/src/cz/cuni/mff/d3s/deeco/scheduler/SingleThreadedScheduler.java
new file mode 100644
index 000000000..064405deb
--- /dev/null
+++ b/jdeeco-core/src/cz/cuni/mff/d3s/deeco/scheduler/SingleThreadedScheduler.java
@@ -0,0 +1,661 @@
+/*
+ * parts taken from java.util.Timer
+ *
+ * Copyright 1999-2007 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code 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
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+package cz.cuni.mff.d3s.deeco.scheduler;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import cz.cuni.mff.d3s.deeco.executor.Executor;
+import cz.cuni.mff.d3s.deeco.model.runtime.api.PeriodicTrigger;
+import cz.cuni.mff.d3s.deeco.model.runtime.api.Trigger;
+import cz.cuni.mff.d3s.deeco.task.Task;
+import cz.cuni.mff.d3s.deeco.task.TaskInvocationException;
+import cz.cuni.mff.d3s.deeco.task.TaskTriggerListener;
+
+public class SingleThreadedScheduler implements Scheduler {
+ /**
+ * Map capturing the SchedulerTaskWrappers for periodic Tasks. These are
+ * mostly important for postponing the Tasks' execution when the previous
+ * execution took longer than the period.
+ */
+ Map periodicEvents = new HashMap<>();
+
+ Set allTasks = new HashSet<>();
+
+ /**
+ * The scheduler task queue. This data structure is shared with the scheduler
+ * thread. The scheduler produces tasks, via its various schedule calls,
+ * and the scheduler thread consumes, executing scheduler tasks as appropriate,
+ * and removing them from the queue when they're obsolete.
+ */
+ private TaskQueue queue = new TaskQueue();
+
+ /**
+ * The scheduler thread.
+ */
+ private SchedulerThread thread = new SchedulerThread(queue);
+
+ /**
+ * This object causes the scheduler's task execution thread to exit
+ * gracefully when there are no live references to the Scheduler object and no
+ * tasks in the scheduler queue. It is used in preference to a finalizer on
+ * Scheduler as such a finalizer would be susceptible to a subclass's
+ * finalizer forgetting to call it.
+ */
+ private Object threadReaper = new Object() {
+ protected void finalize() throws Throwable {
+ synchronized(queue) {
+ thread.newTasksMayBeScheduled = false;
+ queue.notify(); // In case queue is empty.
+ }
+ }
+ };
+
+ /**
+ * If the completed task is periodic and the completion time is greater than the intended start of the next period,
+ * then the next period is moved.
+ */
+ @Override
+ public void executionCompleted(Task task) {
+ if (task instanceof InvokeAndWaitTask)
+ task.notify();
+
+ synchronized (queue) {
+ SchedulerEvent event = periodicEvents.get(task);
+ // continue only for periodic tasks
+ if (event == null)
+ return;
+
+ synchronized (event.lock) {
+ // if the periodic task execution took more than it remained till the next period
+ if (event.nextExecutionTime < System.currentTimeMillis()) {
+ queue.rescheduleTask(event, System.currentTimeMillis() + event.period);
+ }
+ }
+ }
+
+
+ }
+
+ @Override
+ public void executionFailed(Task task, Exception e) {
+ executionCompleted(task);
+ }
+
+ @Override
+ public void start() {
+
+ if (!thread.isAlive())
+ thread.start();
+
+ synchronized(queue) {
+ thread.tasksMayBeExecuted = true;
+ queue.notify(); // In case queue is empty.
+ }
+ }
+
+ /**
+ * Temporarily stop the scheduler.
+ */
+ @Override
+ public void stop() {
+ synchronized(queue) {
+ thread.tasksMayBeExecuted = false;
+ }
+ }
+
+ /**
+ * @throws IllegalStateException if scheduler thread already terminated.
+ * @throws IllegalArgumentException of a null task is passed as an argument.
+ */
+ @Override
+ public void addTask(Task task) {
+ if (task == null)
+ throw new IllegalArgumentException("The task cannot be null");
+
+ synchronized (allTasks) {
+ if (allTasks.contains(task))
+ return;
+
+ synchronized (queue) {
+ if (!thread.newTasksMayBeScheduled)
+ throw new IllegalStateException(
+ "Scheduler already terminated.");
+
+ if (task.getPeriodicTrigger() != null) {
+ SchedulerEvent sTask = new SchedulerEvent(task, task.getPeriodicTrigger());
+ scheduleNow(sTask, task.getPeriodicTrigger().getPeriod());
+ periodicEvents.put(task, sTask);
+ }
+ }
+ task.setTriggerListener(new TaskTriggerListener() {
+ @Override
+ public void triggered(Task task, Trigger trigger) {
+ synchronized (queue) {
+ if (!thread.newTasksMayBeScheduled || !thread.tasksMayBeExecuted)
+ return;
+
+ boolean isScheduled;
+ synchronized (allTasks) {
+ isScheduled = allTasks.contains(task);
+ }
+ if (isScheduled) {
+ scheduleNow(new SchedulerEvent(task, trigger), 0);
+ }
+ }
+ }
+ });
+
+ allTasks.add(task);
+ }
+ }
+
+ /**
+ * Note that this method has to be explicitly protected by queue's monitor!
+ */
+ private void scheduleNow(SchedulerEvent sTask, long period) {
+ sTask.nextExecutionTime = System.currentTimeMillis(); // start immediately
+ sTask.period = period;
+ sTask.state = SchedulerEvent.SCHEDULED;
+
+ queue.add(sTask);
+ if (queue.getMin() == sTask)
+ queue.notify();
+ }
+
+ /**
+ *
+ */
+ @Override
+ public void removeTask(Task task) {
+ synchronized (allTasks) {
+ if (!allTasks.contains(task))
+ return;
+
+ task.unsetTriggerListener();
+ synchronized(queue) {
+ // cancel all the periodic/triggered schedules of the task
+ queue.cancelAll(task);
+ periodicEvents.remove(task);
+ }
+ allTasks.remove(task);
+ }
+ }
+
+ @Override
+ public void setExecutor(Executor executor) {
+ synchronized (thread.executorLock) {
+ thread.executor = executor;
+ }
+ }
+
+ public void invokeAndWait(Runnable doRun) throws InterruptedException {
+ InvokeAndWaitTask task = new InvokeAndWaitTask(this, doRun);
+ addTask(task);
+ task.wait();
+ }
+}
+
+
+
+
+/**
+ * This "helper class" implements the scheduler's task execution thread, which
+ * waits for tasks on the scheduler queue, executions them when they fire,
+ * reschedules repeating tasks, and removes cancelled tasks and spent
+ * non-repeating tasks from the queue.
+ */
+class SchedulerThread extends Thread {
+ /**
+ * This flag is set to false by the reaper to inform us that there
+ * are no more live references to our Scheduler object. Once this flag
+ * is true and there are no more tasks in our queue, there is no
+ * work left for us to do, so we terminate gracefully. Note that
+ * this field is protected by queue's monitor!
+ */
+ boolean newTasksMayBeScheduled = true;
+
+
+ /**
+ * This flag is set to false by scheduler to inform us that the scheduler is
+ * temporarily stopped and all the scheduled tasks have to bee ignored. Note
+ * that this field is protected by queue's monitor!
+ */
+ boolean tasksMayBeExecuted = false;
+
+ /**
+ * Our Scheduler's queue. We store this reference in preference to
+ * a reference to the Scheduler so the reference graph remains acyclic.
+ * Otherwise, the Scheduler would never be garbage-collected and this
+ * thread would never go away.
+ */
+ private TaskQueue queue;
+
+
+ Object executorLock = new Object();
+
+ Executor executor;
+
+ SchedulerThread(TaskQueue queue) {
+ this.queue = queue;
+ }
+
+ public void run() {
+ try {
+ mainLoop();
+ } finally {
+ // Someone killed this Thread, behave as if Scheduler cancelled
+ synchronized(queue) {
+ newTasksMayBeScheduled = false;
+ queue.clear(); // Eliminate obsolete references
+ }
+ }
+ }
+
+ /**
+ * Wait on the queue till the nextExecutionTime of the event, or till
+ * the event is removed from the top of the queue (either by some new event
+ * that has to be executed sooner, or by removing the current event).
+ *
+ * @throws InterruptedException
+ * if the thread gets interrupted during waiting
+ */
+ private boolean waitTaskFired(SchedulerEvent event) throws InterruptedException {
+ while (true) {
+ long currentTime = System.currentTimeMillis();
+ long executionTime = event.nextExecutionTime;
+ boolean taskFired = (executionTime<=currentTime);
+
+ if (!taskFired) { // Task hasn't yet fired; wait
+ queue.wait(executionTime - currentTime);
+
+ // restart the main loop, since the current event might have been
+ // deleted, or some other event that has to be
+ // scheduled earlier might have been added, or all
+ // tasks might have been deleted
+ if (queue.getMin() != event)
+ return false;
+
+ // check taskFired again, i.e., that we were
+ // waken up by waiting long enough (and not by
+ // some other notify, e.g., when adding a task)
+ } else {
+ // we have waited long enough
+ return true;
+ }
+ }
+ }
+
+ /**
+ * The main scheduler loop. (See class comment.)
+ */
+ private void mainLoop() {
+ while (true) {
+ try {
+ SchedulerEvent event;
+ boolean canExecute;
+ synchronized(queue) {
+ // Wait for queue to become non-empty
+ while (queue.isEmpty() && newTasksMayBeScheduled)
+ queue.wait();
+ if (queue.isEmpty() && !newTasksMayBeScheduled)
+ break; // Queue is empty and will forever remain; die
+
+ // Queue nonempty; look at first event and do the right thing
+ event = queue.getMin();
+
+ synchronized(event.lock) {
+ if (event.state == SchedulerEvent.CANCELLED) {
+ queue.removeMin();
+ continue; // No action required, poll queue again
+ }
+
+ canExecute = waitTaskFired(event);
+
+ if (!canExecute)
+ continue; // The event cannot continue with execution, poll queue again
+
+ if (event.period == 0) { // Non-repeating, remove
+ queue.removeMin();
+ event.state = SchedulerEvent.EXECUTED;
+ } else { // Repeating task, reschedule
+ queue.rescheduleMin(event.nextExecutionTime + event.period);
+ }
+ }
+
+ // make sure the fire task can be executed
+ canExecute = canExecute && tasksMayBeExecuted;
+ }
+
+ if (canExecute) { // Task fired and can execute; run it
+ synchronized (executorLock) {
+ executor.execute(event.executable, event.trigger);
+ }
+ }
+ } catch(InterruptedException e) {
+ }
+ }
+ }
+}
+
+/**
+ * This class represents a scheduler task queue: a priority queue of SchedulerTasks,
+ * ordered on nextExecutionTime. Each Scheduler object has one of these, which it
+ * shares with its SchedulerThread. Internally this class uses a heap, which
+ * offers log(n) performance for the add, removeMin and rescheduleMin
+ * operations, and constant time performance for the getMin operation.
+ */
+class TaskQueue {
+ /**
+ * Priority queue represented as a balanced binary heap: the two children
+ * of queue[n] are queue[2*n] and queue[2*n+1]. The priority queue is
+ * ordered on the nextExecutionTime field: The SchedulerEvent with the lowest
+ * nextExecutionTime is in queue[1] (assuming the queue is nonempty). For
+ * each node n in the heap, and each descendant of n, d,
+ * n.nextExecutionTime <= d.nextExecutionTime.
+ */
+ private SchedulerEvent[] queue = new SchedulerEvent[128];
+
+ /**
+ * The number of tasks in the priority queue. (The tasks are stored in
+ * queue[1] up to queue[size]).
+ */
+ private int size = 0;
+
+ /**
+ * Returns the number of tasks currently on the queue.
+ */
+ int size() {
+ return size;
+ }
+
+
+ /**
+ * Adds a new task to the priority queue.
+ */
+ void add(SchedulerEvent task) {
+ // Grow backing store if necessary
+ if (size + 1 == queue.length)
+ queue = Arrays.copyOf(queue, 2*queue.length);
+
+ queue[++size] = task;
+ fixUp(size);
+ }
+
+ /**
+ * Return the "head task" of the priority queue. (The head task is an
+ * task with the lowest nextExecutionTime.)
+ */
+ SchedulerEvent getMin() {
+ if (size > 0)
+ return queue[1];
+ else
+ return null;
+ }
+
+ /**
+ * Return the ith task in the priority queue, where i ranges from 1 (the
+ * head task, which is returned by getMin) to the number of tasks on the
+ * queue, inclusive.
+ */
+ SchedulerEvent get(int i) {
+ return queue[i];
+ }
+
+ /**
+ * Remove the head task from the priority queue.
+ */
+ void removeMin() {
+ queue[1] = queue[size];
+ queue[size--] = null; // Drop extra reference to prevent memory leak
+ fixDown(1);
+ }
+
+ /**
+ * Cancels all the scheduler tasks holding the given executable task from
+ * queue. There can be many of them due to multiple triggers firing at a
+ * rapid succession. The cancelled tasks will be removed automatically by
+ * the SchedulerThreat.
+ */
+ void cancelAll(Task executable) {
+ for (int i=1; i <= size; ++i) {
+ synchronized(queue[i].lock) {
+ if (queue[i].executable.equals(executable)) {
+ queue[i].state = SchedulerEvent.CANCELLED;
+ }
+ }
+ }
+ }
+
+ /**
+ * Sets the nextExecutionTime associated with the head task to the
+ * specified value, and adjusts priority queue accordingly.
+ */
+ void rescheduleMin(long newTime) {
+ queue[1].nextExecutionTime = newTime;
+ fixDown(1);
+ }
+
+ /**
+ * Sets the nextExecutionTime associated with the given scheduler task to
+ * the specified value, and adjusts priority queue accordingly.
+ */
+ public void rescheduleTask(SchedulerEvent task, long newTime) {
+ int i = 1;
+ for (;i <= size; ++i) {
+ if (queue[i].equals(task))
+ break;
+ }
+ // no more occurences found
+ if (i > size)
+ return;
+
+ assert queue[i].nextExecutionTime <= newTime;
+
+ queue[i].nextExecutionTime = newTime;
+ fixDown(i);
+ }
+
+ /**
+ * Returns true if the priority queue contains no elements.
+ */
+ boolean isEmpty() {
+ return size==0;
+ }
+
+ /**
+ * Removes all elements from the priority queue.
+ */
+ void clear() {
+ // Null out task references to prevent memory leak
+ for (int i=1; i<=size; i++)
+ queue[i] = null;
+
+ size = 0;
+ }
+
+ /**
+ * Establishes the heap invariant (described above) assuming the heap
+ * satisfies the invariant except possibly for the leaf-node indexed by k
+ * (which may have a nextExecutionTime less than its parent's).
+ *
+ * This method functions by "promoting" queue[k] up the hierarchy
+ * (by swapping it with its parent) repeatedly until queue[k]'s
+ * nextExecutionTime is greater than or equal to that of its parent.
+ */
+ private void fixUp(int k) {
+ while (k > 1) {
+ int j = k >> 1;
+ if (queue[j].nextExecutionTime <= queue[k].nextExecutionTime)
+ break;
+ SchedulerEvent tmp = queue[j]; queue[j] = queue[k]; queue[k] = tmp;
+ k = j;
+ }
+ }
+
+ /**
+ * Establishes the heap invariant (described above) in the subtree
+ * rooted at k, which is assumed to satisfy the heap invariant except
+ * possibly for node k itself (which may have a nextExecutionTime greater
+ * than its children's).
+ *
+ * This method functions by "demoting" queue[k] down the hierarchy
+ * (by swapping it with its smaller child) repeatedly until queue[k]'s
+ * nextExecutionTime is less than or equal to those of its children.
+ */
+ private void fixDown(int k) {
+ int j;
+ while ((j = k << 1) <= size && j > 0) {
+ if (j < size &&
+ queue[j].nextExecutionTime > queue[j+1].nextExecutionTime)
+ j++; // j indexes smallest kid
+ if (queue[k].nextExecutionTime <= queue[j].nextExecutionTime)
+ break;
+ SchedulerEvent tmp = queue[j]; queue[j] = queue[k]; queue[k] = tmp;
+ k = j;
+ }
+ }
+
+ /**
+ * Establishes the heap invariant (described above) in the entire tree,
+ * assuming nothing about the order of the elements prior to the call.
+ */
+ void heapify() {
+ for (int i = size/2; i >= 1; i--)
+ fixDown(i);
+ }
+}
+
+
+class SchedulerEvent {
+ /**
+ * This object is used to control access to the SchedulerEvent internals.
+ */
+ final Object lock = new Object();
+
+ /**
+ * The state of this task, chosen from the constants below.
+ */
+ int state = VIRGIN;
+
+ /**
+ * This task has not yet been scheduled.
+ */
+ static final int VIRGIN = 0;
+
+ /**
+ * This task is scheduled for execution. If it is a non-repeating task,
+ * it has not yet been executed.
+ */
+ static final int SCHEDULED = 1;
+
+ /**
+ * This non-repeating task has already executed (or is currently
+ * executing) and has not been cancelled.
+ */
+ static final int EXECUTED = 2;
+
+ /**
+ * This task has been cancelled (with a call to SchedulerEvent.cancel).
+ */
+ static final int CANCELLED = 3;
+
+ /**
+ * Next execution time for this task in the format returned by
+ * System.currentTimeMillis, assuming this task is scheduled for execution.
+ * For repeating tasks, this field is updated prior to each task execution.
+ */
+ long nextExecutionTime;
+
+ /**
+ * Period in milliseconds for repeating tasks. A positive value indicates
+ * fixed-rate execution. A value of 0 indicates a non-repeating task.
+ */
+ long period = 0;
+
+ /**
+ * The actual task to be executed.
+ */
+ Task executable;
+
+ /**
+ * The trigger associated with this event.
+ */
+ Trigger trigger;
+
+
+ /**
+ * Creates a new scheduler task.
+ */
+ protected SchedulerEvent(Task task, Trigger trigger) {
+ this.executable = task;
+ this.trigger = trigger;
+ }
+
+
+
+}
+
+/**
+ * Ad-hoc tasks for one-time execution of a runnable within the context of the scheduler thread.
+ *
+ * @author Jaroslav Keznikl
+ *
+ */
+class InvokeAndWaitTask extends Task {
+
+ Runnable runnable;
+
+ public InvokeAndWaitTask(Scheduler scheduler, Runnable runnable) {
+ super(scheduler);
+ this.runnable = runnable;
+ }
+
+ @Override
+ public void invoke(Trigger trigger) throws TaskInvocationException {
+ if (runnable != null) {
+ runnable.run();
+ }
+ }
+
+ @Override
+ protected void registerTriggers() {
+ }
+
+ @Override
+ protected void unregisterTriggers() {
+ }
+
+ @Override
+ public PeriodicTrigger getPeriodicTrigger() {
+ return null;
+ }
+
+}
\ No newline at end of file
diff --git a/jdeeco-core/test/cz/cuni/mff/d3s/deeco/model/runtime/RuntimeModelTest.java b/jdeeco-core/test/cz/cuni/mff/d3s/deeco/model/runtime/RuntimeModelTest.java
index ed522aed1..5ebefb03f 100644
--- a/jdeeco-core/test/cz/cuni/mff/d3s/deeco/model/runtime/RuntimeModelTest.java
+++ b/jdeeco-core/test/cz/cuni/mff/d3s/deeco/model/runtime/RuntimeModelTest.java
@@ -18,6 +18,7 @@
import cz.cuni.mff.d3s.deeco.model.runtime.api.ComponentInstance;
import cz.cuni.mff.d3s.deeco.model.runtime.api.ComponentProcess;
import cz.cuni.mff.d3s.deeco.model.runtime.api.KnowledgePath;
+import cz.cuni.mff.d3s.deeco.model.runtime.api.PathNode;
import cz.cuni.mff.d3s.deeco.model.runtime.api.PathNodeField;
import cz.cuni.mff.d3s.deeco.model.runtime.api.RuntimeMetadata;
import cz.cuni.mff.d3s.deeco.model.runtime.custom.RuntimeMetadataFactoryExt;
@@ -25,7 +26,7 @@
/**
* @author Tomas Bures
- *
+ *
*/
public class RuntimeModelTest {
@@ -38,76 +39,109 @@ private KnowledgePath createSamplePathInstance() {
PathNodeField pn;
KnowledgePath p = factory.createKnowledgePath();
-
+
pn = factory.createPathNodeField();
pn.setName(new String("level1"));
p.getNodes().add(pn);
-
+
pn = factory.createPathNodeField();
pn.setName(new String("level2"));
p.getNodes().add(pn);
-
+
return p;
}
-
+
+ private PathNode createPathNodeInstance(String name) {
+ RuntimeMetadataFactory factory = RuntimeMetadataFactory.eINSTANCE;
+ PathNodeField pn = factory.createPathNodeField();
+ pn.setName(new String(name));
+ return pn;
+ }
+
+ @Test
+ public void testEqualsWorksWithPathNodes() {
+ // WHEN two instance of PathNode designate the same path node
+ PathNode pn1 = createPathNodeInstance("test");
+ PathNode pn2 = createPathNodeInstance("test");
+ // THEN the two instances of PathNode are equal w.r.t equals method
+ assertEquals(pn1, pn2);
+ }
+
+ @Test
+ public void testHashCodeWorksWithPathNodes() {
+ // WHEN two instance of PathNode designate the same path node
+ PathNode pn1 = createPathNodeInstance("test");
+ PathNode pn2 = createPathNodeInstance("test");
+ // THEN the two instances of PathNode are equal w.r.t hashCode method
+ assertEquals(pn1.hashCode(), pn2.hashCode());
+ }
+
@Test
public void testEqualsWorksWithKnowledgePath() {
-
// WHEN two instance of KnowledgePath designate the same path
KnowledgePath p1 = createSamplePathInstance();
KnowledgePath p2 = createSamplePathInstance();
- // THEN they should be equal using equals method
+ // THEN they should be equal using equals method
assertEquals(p1, p2);
}
+ @Test
+ public void testHashCodeWorksWithKnowledgePath() {
+ // WHEN two instance of KnowledgePath designate the same path
+ KnowledgePath p1 = createSamplePathInstance();
+ KnowledgePath p2 = createSamplePathInstance();
+ // WHEN the two instance of KnowledgePath are equal w.r.t hashCode methods
+ assertEquals(p1.hashCode(), p2.hashCode());
+ }
+
@Test
public void testExtensions() {
// WHEN a RuntimeMetadataFactory is obtained
- RuntimeMetadataFactory factory = RuntimeMetadataFactory.eINSTANCE;
- // THEN RuntimeMetadataFactory is an instance of RuntimeMetadataFactoryExt (i.e. our custom class)
+ RuntimeMetadataFactory factory = RuntimeMetadataFactory.eINSTANCE;
+ // THEN RuntimeMetadataFactory is an instance of
+ // RuntimeMetadataFactoryExt (i.e. our custom class)
assertTrue(factory instanceof RuntimeMetadataFactoryExt);
}
-
+
public static void dummyMethodThatStandsForAComponentProcess() {
}
+ @Test
+ @Ignore
+ public void testSaveAndLoad() throws Exception {
+ // WHEN a RuntimeMetadata instance, which contains a method (within an Invocable)
+ // and contains a knowledge manager with some knowledge is created
+ SampleRuntimeModel oModel = new SampleRuntimeModel();
+
+ // THEN the instance can be saved
+ ResourceSet resourceSet = new ResourceSetImpl();
- @Test
- @Ignore
- public void testSaveAndLoad() throws Exception {
- // WHEN a RuntimeMetadata instance, which contains a method (within an Invocable)
- // and contains a knowledge manager with some knowledge is created
- SampleRuntimeModel oModel = new SampleRuntimeModel();
-
- // THEN the instance can be saved
- ResourceSet resourceSet = new ResourceSetImpl();
-
- resourceSet.getResourceFactoryRegistry().getExtensionToFactoryMap().put(Resource.Factory.Registry.DEFAULT_EXTENSION, new XMIResourceFactoryImpl());
-
- File testXMIFile = new File("test-temp/test.xmi");
- URI fileURI = URI.createFileURI(testXMIFile.getAbsolutePath());
- Resource resource = resourceSet.createResource(fileURI);
- resource.getContents().add(oModel.model);
- resource.save(Collections.EMPTY_MAP);
- assertTrue(testXMIFile.exists());
-
- // WHEN a RuntimeMetadata instance with a method is loaded
- resourceSet = new ResourceSetImpl();
-
- // This needs to be uncommented, but for that it needs a dependency on XMI
- // resourceSet.getResourceFactoryRegistry().getExtensionToFactoryMap().put(Resource.Factory.Registry.DEFAULT_EXTENSION, new XMIResourceFactoryImpl());
-
- RuntimeMetadata nModel = (RuntimeMetadata)resourceSet.getResource(fileURI, true).getContents().get(0);
-
- // THEN it has the same values
- // AND refers to the same method within the same class as before
- // AND contains the same knowledge as before
- ComponentInstance nComponentInstance = nModel.getComponentInstances().get(0);
- ComponentProcess nComponentProcess = nComponentInstance.getComponentProcesses().get(0);
-
- assertEquals(oModel.componentInstance.getName(), nComponentInstance.getName());
- assertEquals(oModel.process.getName(), nComponentProcess.getName());
- assertEquals(oModel.process.getMethod(), nComponentProcess.getMethod());
- // TODO: Compare the triggers and process params
- // TODO: Compare the data stored in the knowledge manager
- }
-}
+ resourceSet.getResourceFactoryRegistry().getExtensionToFactoryMap().put(Resource.Factory.Registry.DEFAULT_EXTENSION, new XMIResourceFactoryImpl());
+
+ File testXMIFile = new File("test-temp/test.xmi");
+ URI fileURI = URI.createFileURI(testXMIFile.getAbsolutePath());
+ Resource resource = resourceSet.createResource(fileURI);
+ resource.getContents().add(oModel.model);
+ resource.save(Collections.EMPTY_MAP);
+ assertTrue(testXMIFile.exists());
+
+ // WHEN a RuntimeMetadata instance with a method is loaded
+ resourceSet = new ResourceSetImpl();
+
+ // This needs to be uncommented, but for that it needs a dependency on XMI
+ // resourceSet.getResourceFactoryRegistry().getExtensionToFactoryMap().put(Resource.Factory.Registry.DEFAULT_EXTENSION, new XMIResourceFactoryImpl());
+
+ RuntimeMetadata nModel = (RuntimeMetadata)resourceSet.getResource(fileURI, true).getContents().get(0);
+
+ // THEN it has the same values
+ // AND refers to the same method within the same class as before
+ // AND contains the same knowledge as before
+ ComponentInstance nComponentInstance = nModel.getComponentInstances().get(0);
+ ComponentProcess nComponentProcess = nComponentInstance.getComponentProcesses().get(0);
+
+ assertEquals(oModel.componentInstance.getName(), nComponentInstance.getName());
+ assertEquals(oModel.process.getName(), nComponentProcess.getName());
+ assertEquals(oModel.process.getMethod(), nComponentProcess.getMethod());
+ // TODO: Compare the triggers and process params
+ // TODO: Compare the data stored in the knowledge manager
+ }
+}
diff --git a/jdeeco-core/test/cz/cuni/mff/d3s/deeco/scheduler/LocalTimeSchedulerTest.java b/jdeeco-core/test/cz/cuni/mff/d3s/deeco/scheduler/LocalTimeSchedulerTest.java
index 095f3382d..14b3b87c5 100644
--- a/jdeeco-core/test/cz/cuni/mff/d3s/deeco/scheduler/LocalTimeSchedulerTest.java
+++ b/jdeeco-core/test/cz/cuni/mff/d3s/deeco/scheduler/LocalTimeSchedulerTest.java
@@ -3,7 +3,7 @@
import cz.cuni.mff.d3s.deeco.executor.Executor;
/**
- * Factory for Scheduler implementation tests
+ *
*
* @author Jaroslav Keznikl
*
diff --git a/jdeeco-core/test/cz/cuni/mff/d3s/deeco/scheduler/SchedulerTest.java b/jdeeco-core/test/cz/cuni/mff/d3s/deeco/scheduler/SchedulerTest.java
index 729f32da3..9bfd81c94 100644
--- a/jdeeco-core/test/cz/cuni/mff/d3s/deeco/scheduler/SchedulerTest.java
+++ b/jdeeco-core/test/cz/cuni/mff/d3s/deeco/scheduler/SchedulerTest.java
@@ -1,25 +1,26 @@
-package cz.cuni.mff.d3s.deeco.scheduler;
-
-import static org.mockito.Matchers.any;
-import static org.mockito.Mockito.atLeastOnce;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.timeout;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-
-import cz.cuni.mff.d3s.deeco.executor.Executor;
-import cz.cuni.mff.d3s.deeco.model.runtime.api.PeriodicTrigger;
-import cz.cuni.mff.d3s.deeco.model.runtime.api.Trigger;
-import cz.cuni.mff.d3s.deeco.task.Task;
-import cz.cuni.mff.d3s.deeco.task.TaskTriggerListener;
-
+package cz.cuni.mff.d3s.deeco.scheduler;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+
+import cz.cuni.mff.d3s.deeco.executor.Executor;
+import cz.cuni.mff.d3s.deeco.model.runtime.api.PeriodicTrigger;
+import cz.cuni.mff.d3s.deeco.model.runtime.api.Trigger;
+import cz.cuni.mff.d3s.deeco.task.Task;
+import cz.cuni.mff.d3s.deeco.task.TaskTriggerListener;
+
/**
* Scheduler test suite
*
@@ -27,199 +28,203 @@
* @author Andranik Muradyan
*
* */
-public abstract class SchedulerTest {
-
- protected Scheduler tested;
- protected Executor executor;
- protected TaskTriggerListener testListener;
-
- protected abstract Scheduler setUpTested(Executor executor2);
-
-
- @Before
- public void setUp() throws Exception{
- executor = mock(Executor.class);
- tested = setUpTested(executor);
- testListener = mock(TaskTriggerListener.class);
- }
-
- @After
- public void tearDown() throws Exception{
- if (tested!=null)
- tested.stop();
- }
-
- // TODO TB: Shouldn't we have also a test that tests repeated execution? For instnce to start the scheduler, let it run for 2 secs and see that a periodic
- // task with a period of 100 ms got scheduled approx. 200 times?
-
- @Test
- public void testPeriodicTaskScheduledWhenSchedulerStarted() throws InterruptedException {
- Task t = mock(Task.class);
- PeriodicTrigger p = mock(PeriodicTrigger.class);
- when(p.getPeriod()).thenReturn(11L);
- when(t.getPeriodicTrigger()).thenReturn(p);
-
- // WHEN a periodic task is added to a new (stopped) scheduler
- tested.addTask(t);
- // THEN the task is not scheduled
- verify(executor, timeout(10).never()).execute(t, p);
-
- // WHEN the scheduler is started, runs for a while (longer than the
- // period) and then stopped
- tested.start();
- Thread.sleep(10);
- tested.stop();
- // THEN the task gets eventually scheduled
- verify(executor, atLeastOnce()).execute(t, p);
-
- reset(executor);
-
- // WHEN the running scheduler is stopped a bit longer (FIXME TB: not sure what it means)
- // THEN the task is no longer scheduled
- verify(executor, timeout(10).never()).execute(t, p);
-
- }
-
- @Test
- public void testPeriodicTaskAutomaticallyScheduledWhenAddedToRunningScheduler() throws InterruptedException {
- Task t = mock(Task.class);
- PeriodicTrigger p = mock(PeriodicTrigger.class);
- when(p.getPeriod()).thenReturn(11L);
- when(t.getPeriodicTrigger()).thenReturn(p);
- tested.start();
-
- // WHEN a task is added to a running scheduler
- tested.addTask(t);
- // THEN it gets eventually scheduled
- verify(executor, timeout(10).atLeastOnce()).execute(t, p);
- }
-
- @Test
- public void testPeriodicTaskNotScheduledWhenRemovedRunningScheduler() throws InterruptedException {
- Task t = mock(Task.class);
- PeriodicTrigger p = mock(PeriodicTrigger.class);
- when(p.getPeriod()).thenReturn(11L);
- when(t.getPeriodicTrigger()).thenReturn(p);
-
- tested.addTask(t);
- tested.start();
-
- // WHEN a task is removed from a running scheduler
- tested.removeTask(t);
- // THEN it gets eventually un-scheduled
- reset(executor);
- verify(executor, timeout(10).never()).execute(t, p);
- }
-
- @Test
- public void testTriggeredTaskScheduledOnlyWhenTriggered() throws InterruptedException {
- Task t = createTriggeredTask();
- Trigger tr = mock(Trigger.class);
-
- // WHEN a triggered task is added to a stopped scheduler and the trigger is triggered
- tested.addTask(t);
- testListener.triggered(t, tr);
- // THEN the process in not scheduled
- verify(executor, never()).execute(t, tr);
-
- // WHEN the scheduler is started with a registered triggered task
- tested.start();
- // THEN it is not scheduled if no trigger is triggered
- verify(executor, timeout(10).never()).execute(t, tr);
-
- // WHEN the corresponding trigger is triggered
- testListener.triggered(t, tr);
- // THEN the process is scheduled (exactly once)
- verify(executor, times(1)).execute(t, tr);
-
- // WHEN the scheduler is stopped and the trigger is triggered
- reset(executor);
- tested.stop();
- testListener.triggered(t, tr);
- // THEN the process in not scheduled anymore
- verify(executor, never()).execute(t, tr);
-
- // WHEN the task is removed from a running scheduler and the trigger is triggered
- tested.start();
- tested.removeTask(t);
- testListener.triggered(t, tr);
- // THEN the process in not scheduled
- verify(executor, never()).execute(t, tr);
- }
-
- @Test
- public void testTriggerListenerRegisteredAfterAddWhenRunning() {
- Task t = mock(Task.class);
-
- tested.start();
-
- // WHEN a task is added to a running scheduler
- tested.addTask(t);
- // THEN the scheduler registers a trigger listener for the task
- verify(t, times(1)).setTriggerListener(any(TaskTriggerListener.class));
-
- // WHEN repeating the action
- reset(t);
- tested.addTask(t);
- // THEN nothing happens anymore
- verify(t, never()).setTriggerListener(any(TaskTriggerListener.class));
- }
-
- @Test
- public void testTriggerListenerUnregisteredAfterRemoveWhenRunning() {
- Task t = mock(Task.class);
- tested.start();
- tested.addTask(t);
-
- // WHEN a task is removed from a running scheduler
- tested.removeTask(t);
-
- // THEN the scheduler unregisters its trigger listener for the task
- verify(t, times(1)).setTriggerListener(null);
-
- // WHEN repeating the action
- reset(t);
- tested.removeTask(t);
- // THEN nothing happens anymore
- verify(t, never()).setTriggerListener(null);
- }
-
- @Test
- public void testTriggerListenerRegisteredAfterStartWhenAdded() {
- Task t = mock(Task.class);
- tested.addTask(t);
-
- // WHEN a scheduler with a single added task is started
- tested.start();
- // THEN the scheduler registers a trigger listener for the task
- verify(t, times(1)).setTriggerListener(any(TaskTriggerListener.class));
-
- // WHEN repeating the action
- reset(t);
- tested.start();
- // THEN nothing happens anymore
- verify(t, never()).setTriggerListener(any(TaskTriggerListener.class));
- }
-
- @Test
- public void testTriggerListenerUnregisteredAfterStopWhenAdded() {
- Task t = mock(Task.class);
- tested.start();
- tested.addTask(t);
-
- // WHEN a scheduler with a single added task is stopped
- tested.stop();
-
- // THEN the scheduler unregisters its trigger listener for the task
- verify(t, times(1)).setTriggerListener(null);
-
- // WHEN repeating the action
- reset(t);
- tested.stop();
- // THEN nothing happens anymore
- verify(t, never()).setTriggerListener(null);
- }
-
+public abstract class SchedulerTest {
+
+ protected Scheduler tested;
+ protected Executor executor;
+ protected TaskTriggerListener testListener;
+
+ protected abstract Scheduler setUpTested(Executor executor2);
+
+
+ @Before
+ public void setUp() throws Exception{
+ executor = mock(Executor.class);
+ tested = setUpTested(executor);
+ testListener = mock(TaskTriggerListener.class);
+ }
+
+ @After
+ public void tearDown() throws Exception{
+ if (tested!=null)
+ tested.stop();
+ }
+
+ // TODO TB: Shouldn't we have also a test that tests repeated execution? For instnce to start the scheduler, let it run for 2 secs and see that a periodic
+ // task with a period of 100 ms got scheduled approx. 200 times?
+
+ @Test
+ public void testPeriodicTaskScheduledWhenSchedulerStarted() throws InterruptedException {
+ Task t = mock(Task.class);
+ PeriodicTrigger p = mock(PeriodicTrigger.class);
+ when(p.getPeriod()).thenReturn(11L);
+ when(t.getPeriodicTrigger()).thenReturn(p);
+
+ // WHEN a periodic task is added to a new (stopped) scheduler
+ tested.addTask(t);
+ // THEN the task is not scheduled
+ verify(executor, timeout(10).never()).execute(t, p);
+
+ // WHEN the scheduler is started, runs for a while (longer than the
+ // period) and then stopped
+ tested.start();
+ Thread.sleep(10);
+ tested.stop();
+ // THEN the task gets eventually scheduled
+ verify(executor, atLeastOnce()).execute(t, p);
+
+ reset(executor);
+
+ // WHEN the running scheduler is stopped a bit longer (FIXME TB: not sure what it means)
+ // THEN the task is no longer scheduled
+ verify(executor, timeout(10).never()).execute(t, p);
+
+ }
+
+ @Test
+ public void testPeriodicTaskAutomaticallyScheduledWhenAddedToRunningScheduler() throws InterruptedException {
+ Task t = mock(Task.class);
+ PeriodicTrigger p = mock(PeriodicTrigger.class);
+ when(p.getPeriod()).thenReturn(11L);
+ when(t.getPeriodicTrigger()).thenReturn(p);
+ tested.start();
+
+ // WHEN a task is added to a running scheduler
+ tested.addTask(t);
+ // THEN it gets eventually scheduled
+ verify(executor, timeout(10).atLeastOnce()).execute(t, p);
+ }
+
+ @Test
+ public void testPeriodicTaskNotScheduledWhenRemovedRunningScheduler() throws InterruptedException {
+ Task t = mock(Task.class);
+ PeriodicTrigger p = mock(PeriodicTrigger.class);
+ when(p.getPeriod()).thenReturn(11L);
+ when(t.getPeriodicTrigger()).thenReturn(p);
+
+ tested.addTask(t);
+ tested.start();
+
+ // WHEN a task is removed from a running scheduler
+ tested.removeTask(t);
+ // THEN it gets eventually un-scheduled
+ reset(executor);
+ verify(executor, timeout(10).never()).execute(t, p);
+ }
+
+ @Test
+ public void testTriggeredTaskScheduledOnlyWhenTriggered() throws InterruptedException {
+ Task t = createTriggeredTask();
+ Trigger tr = mock(Trigger.class);
+
+ // WHEN a triggered task is added to a stopped scheduler and the trigger is triggered
+ tested.addTask(t);
+ testListener.triggered(t, tr);
+ // THEN the process in not scheduled
+ verify(executor, never()).execute(t, tr);
+
+ // WHEN the scheduler is started with a registered triggered task
+ tested.start();
+ // THEN it is not scheduled if no trigger is triggered
+ verify(executor, timeout(100).never()).execute(t, tr);
+
+ // WHEN the corresponding trigger is triggered
+ testListener.triggered(t, tr);
+ // THEN the process is scheduled (exactly once)
+ // (we use a small timeout because the scheduler might have a separate thread for scheduling)
+ verify(executor, timeout(20).times(1)).execute(t, tr);
+
+
+ // WHEN the scheduler is stopped and the trigger is triggered
+ tested.stop();
+ reset(executor);
+ testListener.triggered(t, tr);
+ // THEN the process in not scheduled anymore
+ verify(executor, never()).execute(t, tr);
+
+ // WHEN the task is removed from a running scheduler and the trigger is triggered
+ tested.start();
+ tested.removeTask(t);
+ testListener.triggered(t, tr);
+ // THEN the process in not scheduled
+ verify(executor, never()).execute(t, tr);
+ }
+
+ @Test
+ public void testTriggerListenerRegisteredAfterAddWhenRunning() {
+ Task t = mock(Task.class);
+
+ tested.start();
+
+ // WHEN a task is added to a running scheduler
+ tested.addTask(t);
+ // THEN the scheduler registers a trigger listener for the task
+ verify(t, times(1)).setTriggerListener(any(TaskTriggerListener.class));
+
+ // WHEN repeating the action
+ reset(t);
+ tested.addTask(t);
+ // THEN nothing happens anymore
+ verify(t, never()).setTriggerListener(any(TaskTriggerListener.class));
+ }
+
+ @Test
+ public void testTriggerListenerUnregisteredAfterRemoveWhenRunning() {
+ Task t = mock(Task.class);
+ tested.start();
+ tested.addTask(t);
+
+ // WHEN a task is removed from a running scheduler
+ tested.removeTask(t);
+
+ // THEN the scheduler unregisters its trigger listener for the task
+ verify(t, times(1)).unsetTriggerListener();
+
+ // WHEN repeating the action
+ reset(t);
+ tested.removeTask(t);
+ // THEN nothing happens anymore
+ verify(t, never()).unsetTriggerListener();
+ }
+
+ @Test
+ public void testTriggerListenerRegisteredAfterStartWhenAdded() {
+ Task t = mock(Task.class);
+ tested.addTask(t);
+
+ // WHEN a scheduler with a single added task is started
+ tested.start();
+ // THEN the scheduler registers a trigger listener for the task
+ verify(t, times(1)).setTriggerListener(any(TaskTriggerListener.class));
+
+ // WHEN repeating the action
+ reset(t);
+ tested.start();
+ // THEN nothing happens anymore
+ verify(t, never()).setTriggerListener(any(TaskTriggerListener.class));
+ }
+
+ // TODO: decide whether this is really needed
+ @Test
+ @Ignore
+ public void testTriggerListenerUnregisteredAfterStopWhenAdded() {
+ Task t = mock(Task.class);
+ tested.start();
+ tested.addTask(t);
+
+ // WHEN a scheduler with a single added task is stopped
+ tested.stop();
+
+ // THEN the scheduler unregisters its trigger listener for the task
+ verify(t, times(1)).unsetTriggerListener();
+
+ // WHEN repeating the action
+ reset(t);
+ tested.stop();
+ // THEN nothing happens anymore
+ verify(t, never()).unsetTriggerListener();
+ }
+
/**
* Creates a purely triggered task which stores the given trigger listener
* into {@link #testListener}.
diff --git a/jdeeco-core/test/cz/cuni/mff/d3s/deeco/scheduler/SingleThreadedSchedulerTest.java b/jdeeco-core/test/cz/cuni/mff/d3s/deeco/scheduler/SingleThreadedSchedulerTest.java
new file mode 100644
index 000000000..72602ec8b
--- /dev/null
+++ b/jdeeco-core/test/cz/cuni/mff/d3s/deeco/scheduler/SingleThreadedSchedulerTest.java
@@ -0,0 +1,19 @@
+package cz.cuni.mff.d3s.deeco.scheduler;
+
+import cz.cuni.mff.d3s.deeco.executor.Executor;
+
+/**
+ *
+ *
+ * @author Jaroslav Keznikl
+ *
+ */
+public class SingleThreadedSchedulerTest extends SchedulerTest {
+
+ @Override
+ protected Scheduler setUpTested(Executor executor) {
+ Scheduler s = new SingleThreadedScheduler();
+ s.setExecutor(executor);
+ return s;
+ }
+}
\ No newline at end of file